2013-07-10 42 views
12

Gần đây tôi đã chạy một dự án nghiên cứu nhỏ về hiệu suất truy cập tuần tự hoặc ngẫu nhiên tới một NSArray liên quan đến một mảng C. Hầu hết các trường hợp kiểm tra được hiển thị như tôi mong đợi tuy nhiên một số không làm việc như thế nào tôi nghĩ rằng họ sẽ và tôi hy vọng ai đó có thể giải thích tại sao.So sánh hiệu suất của NSArray so với C Array

Về cơ bản thử nghiệm bao gồm điền một mảng C với các đối tượng 50k, lặp qua từng đối tượng và gọi phương thức (trong đó chỉ tăng một phao trong đối tượng), phần thứ hai của thử nghiệm liên quan đến việc tạo vòng lặp hoàn thành 50k lặp lại nhưng truy cập một đối tượng ngẫu nhiên trong mảng. Về cơ bản nó khá đơn giản.

Để thực hiện so sánh, tôi đang khởi tạo NSArray với mảng C. Mỗi bài kiểm tra sau đó được chạy qua một khối được truyền vào một phương thức theo dõi thời gian cần thiết để thực thi khối. Mã tôi đang sử dụng được chứa dưới đây nhưng tôi muốn bao gồm các kết quả và truy vấn tôi có đầu tiên.

Các thử nghiệm này được chạy trên iPhone 4 và được bao bọc trong dispatch_after để giảm thiểu bất kỳ hoạt động luồng hoặc hoạt động không nguyên tử nào còn lại do khởi động ứng dụng. Kết quả của một hoạt động đơn lẻ như sau, mỗi lần chạy là về cơ bản giống với các biến thể nhỏ:

===SEQUENCE=== 
NSARRAY FAST ENUMERATION: 12ms 
NSARRAY FAST ENUMERATION WEAK: 186ms 
NSARRAY BLOCK ENUMERATION: 31ms (258.3%) 
C ARRAY DIRECT: 7ms (58.3%) 
C ARRAY VARIABLE ASSIGN: 33ms (275.0%) 
C ARRAY VARIABLE ASSIGN WEAK: 200ms (1666.7%) 

===RANDOM=== 
NSARRAY RANDOM: 102ms (850.0%) *Relative to fast enumeration 
C ARRAY DIRECT RANDOM: 39ms (38.2%) *Relative to NSArray Random 
C ARRAY VARIABLE ASSIGN RANDOM: 82ms (80.4%) 

Cách tiếp cận nhanh nhất dường như được tiếp cận trực tiếp các mục trong thư mục C Mảng sử dụng "* (carray + idx)" , điều khó hiểu nhất là việc gán con trỏ từ mảng C tới biến c mục tiêu "id object = * (carry + idx)" gây ra một hit hiệu năng lớn. Tôi thấy ban đầu nó có thể là vòng cung làm một cái gì đó với tính tham chiếu như biến mạnh vì vậy tại thời điểm này tôi đã thay đổi nó thành yếu mong đợi hiệu suất tăng "__weak id object = * (carry + idx)". Ngạc nhiên thay, nó thực sự chậm hơn rất nhiều.

Kết quả truy cập ngẫu nhiên diễn ra khá tốt như thế nào tôi mong đợi dựa trên kết quả chuỗi, do đó, không có bất ngờ có may mắn đủ.

Như một kết quả của việc này có một số câu hỏi:

  1. Tại sao gán cho một biến mất quá lâu?
  2. Tại sao việc gán cho một biến yếu thậm chí còn lâu hơn? (Có thể có điều gì đó mà tôi không hiểu đang diễn ra ở đây)
  3. Xem xét ở trên làm sao Apple có được liệt kê nhanh tiêu chuẩn để thực hiện tốt như vậy?

Và để có đầy đủ, đây là mã. Vì vậy, tôi đang tạo ra các mảng thể như sau:

__block id __strong *cArrayData = (id __strong *)malloc(sizeof(id) * ITEM_COUNT); 

for (NSUInteger idx = 0; idx < ITEM_COUNT; idx ++) { 
    NSTestObject *object = [[NSTestObject alloc] init]; 
    cArrayData[idx] = object; 
} 

__block NSArray *arrayData = [NSArray arrayWithObjects:cArrayData count:ITEM_COUNT]; 

Và NSTestObject được định nghĩa như thế này:

@interface NSTestObject : NSObject 

- (void)doSomething; 

@end 

@implementation NSTestObject 
{ 
    float f; 
} 

- (void)doSomething 
{ 
    f++; 
} 

Và phương pháp sử dụng để cấu hình các mã:

int machTimeToMS(uint64_t machTime) 
{ 
    const int64_t kOneMillion = 1000 * 1000; 
    static mach_timebase_info_data_t s_timebase_info; 

    if (s_timebase_info.denom == 0) { 
     (void) mach_timebase_info(&s_timebase_info); 
    } 
    return (int)((machTime * s_timebase_info.numer)/(kOneMillion * s_timebase_info.denom)); 
} 

- (int)profile:(dispatch_block_t)call name:(NSString *)name benchmark:(int)benchmark 
{ 

    uint64_t startTime, stopTime; 
    startTime = mach_absolute_time(); 

    call(); 

    stopTime = mach_absolute_time(); 

    int duration = machTimeToMS(stopTime - startTime); 

    if (benchmark > 0) { 
     NSLog(@"%@: %i (%0.1f%%)", name, duration, ((float)duration/(float)benchmark) * 100.0f); 
    } else { 
     NSLog(@"%@: %i", name, duration); 
    } 

    return duration; 

} 

Cuối cùng đây là cách tôi đang thực hiện các thử nghiệm thực tế:

int benchmark = [self profile:^ { 
    for (NSTestObject *view in arrayData) { 
     [view doSomething]; 
    } 
} name:@"NSARRAY FAST ENUMERATION" benchmark:0]; 

[self profile:^ { 
    for (NSTestObject __weak *view in arrayData) { 
     [view doSomething]; 
    } 
} name:@"NSARRAY FAST ENUMERATION WEAK" benchmark:0]; 

[self profile:^ { 
    [arrayData enumerateObjectsUsingBlock:^(NSTestObject *view, NSUInteger idx, BOOL *stop) { 
     [view doSomething]; 
    }]; 
} name:@"NSARRAY BLOCK ENUMERATION" benchmark:benchmark]; 

[self profile:^ { 
    for (NSUInteger idx = 0; idx < ITEM_COUNT; idx ++) { 
     [*(cArrayData + idx) doSomething]; 
    } 
} name:@"C ARRAY DIRECT" benchmark:benchmark]; 

[self profile:^ { 
    id object = nil; 
    NSUInteger idx = 0; 
    while (idx < ITEM_COUNT) { 
     object = (id)*(cArrayData + idx); 
     [object doSomething]; 
     object = nil; 
     idx++; 
    } 
} name:@"C ARRAY VARIABLE ASSIGN" benchmark:benchmark]; 

[self profile:^ { 
    __weak id object = nil; 
    NSUInteger idx = 0; 
    while (idx < ITEM_COUNT) { 
     object = (id)*(cArrayData + idx); 
     [object doSomething]; 
     object = nil; 
     idx++; 
    } 
} name:@"C ARRAY VARIABLE ASSIGN WEAK" benchmark:benchmark]; 

NSLog(@"\n===RANDOM===\n"); 

benchmark = [self profile:^ { 
    id object = nil; 
    for (NSUInteger idx = 0; idx < ITEM_COUNT; idx ++) { 
     object = arrayData[arc4random()%ITEM_COUNT]; 
     [object doSomething]; 
    } 
} name:@"NSARRAY RANDOM" benchmark:benchmark]; 

[self profile:^ { 
    NSUInteger idx = 1; 
    while (idx < ITEM_COUNT) { 
     [*(cArrayData + arc4random()%ITEM_COUNT) doSomething]; 
     idx++; 
    } 
} name:@"C ARRAY DIRECT RANDOM" benchmark:benchmark]; 

[self profile:^ { 
    id object = nil; 
    NSUInteger idx = 0; 
    while (idx < ITEM_COUNT) { 
     object = (id)*(cArrayData + arc4random()%ITEM_COUNT); 
     [object doSomething]; 
     idx++; 
    } 
} name:@"C ARRAY VARIABLE ASSIGN RANDOM" benchmark:benchmark]; 
+1

Bắt buộc đọc: [Vô lý cá: Mảng] (http://ridiculousfish.com/blog/posts/array.html) – Caleb

Trả lời

6

Tại sao việc gán cho một biến mất quá lâu?

đoán của bạn là đúng: ARC gọi retain khi bạn chỉ định, và release khi bạn phân công lại, hoặc khi id đi ra khỏi phạm vi.

Tại sao việc gán cho biến yếu thậm chí còn lâu hơn? (Có thể có điều gì đó tôi không hiểu đang diễn ra ở đây)

Nhớ lại tham chiếu yếu của bạn để xóa tham chiếu yếu khi tham chiếu mạnh cuối cùng đã biến mất. Đó là lý do tại sao tài liệu tham khảo yếu kém hơn: để nil ra khỏi __weak id, ARC đăng ký địa chỉ của id với thời gian chạy để nhận thông báo về đối tượng đang được giải phóng. Việc đăng ký này yêu cầu phải viết vào bảng băm - chậm hơn rất nhiều so với việc duy trì và phát hành.

Xem xét ở trên làm thế nào để Apple có được liệt kê nhanh tiêu chuẩn để hoạt động tốt?

Liệt kê nhanh các khối sử dụng của mảng quay lại trực tiếp NSArray. Về cơ bản, họ lấy một khối 30 yếu tố hoặc hơn, và điều trị truy cập vào nó như là một mảng C đơn giản. Sau đó, họ lấy khối tiếp theo, lặp lại trên nó như thể nó là một mảng C, và như vậy. Có một số chi phí nhỏ, nhưng nó là mỗi khối, không phải cho mỗi yếu tố, do đó bạn sẽ có được một hiệu suất khá ấn tượng.

+0

Cảm ơn bạn đã trả lời đã thực sự cố gắng grabbing khối lên đến 10 yếu tố trong vòng lặp và gán cho họ, nhưng nó đã gần như bằng không có hiệu lực về hiệu suất như hầu hết thời gian vòng lặp (~ 80%) được chi cho việc chuyển nhượng biến. Lời giải thích duy nhất tôi có thể nghĩ ngay bây giờ là việc liệt kê nhanh giữ lại từng khối đối tượng và bằng cách nào đó giải phóng tất cả chúng trên một chuỗi nền trong khi nó đang chạy khối tiếp theo. Nhưng tôi không có cách nào để làm lại điều này. – Andy

+3

@Andy Tôi không nghĩ rằng liệt kê nhanh giữ lại và giải phóng các đối tượng ở tất cả: Tôi nghĩ rằng họ sử dụng '__unsafe_unretained', bởi vì đối tượng đã được sở hữu bởi mảng. – dasblinkenlight

+0

Bạn là anh hùng của tôi. Một sự thay đổi nhanh chóng của __weak thành __unsafe_unretained và hiệu năng bây giờ là 8ms và nhanh hơn sau đó là NSArray. Tôi đã không nhận ra có một lợi ích hiệu suất như vậy khi sử dụng __unsafe_unretained nhưng khi bạn nói đúng một tham chiếu __weak phải đặt giá trị thành nil khi nó bị loại bỏ khỏi phạm vi sao cho nó có ý nghĩa. Cảm ơn bạn rất nhiều vì đã giúp đỡ của bạn! – Andy

0

2) vì bạn không có khả năng tối ưu hóa. Biến yếu chứa trong phần tĩnh của bộ nhớ và truy cập vào tĩnh dài hơn động