Niedawno przeprowadziłem mały projekt badawczy dotyczący wydajności sekwencyjnego lub losowego dostępu do NSArray w odniesieniu do C Array. Większość przypadków testowych pojawia się tak, jak się spodziewałem, jednak kilka z nich nie działa tak, jak myślałem, i mam nadzieję, że ktoś może być w stanie wyjaśnić dlaczego.Porównywanie wydajności NSArray vs C Array
Zasadniczo test polega na wypełnieniu tablicy C obiektami 50k, iteracji po każdym z nich i wywołaniu metody (która wewnętrznie po prostu zwiększa wartość zmiennoprzecinkową w obiekcie), druga część testu polega na utworzeniu pętli, która kończy 50k iteracje, ale uzyskuje dostęp do losowego obiektu w tablicy. Zasadniczo jest to dość proste.
Aby wykonać porównanie, inicjalizuję NSArray przy pomocy C Array. Każdy test jest następnie uruchamiany za pośrednictwem bloku przekazywanego do metody, która śledzi czas potrzebny na wykonanie bloku. Kod, którego używam, jest zawarty poniżej, ale chciałbym omówić wyniki i zapytania, które najpierw mam.
Te testy zostały uruchomione na iPhone 4 i owinięte w dispatch_after, aby złagodzić wszelkie pozostałe wątki lub operacje niepodzielone w wyniku uruchomienia aplikacji. Wyniki jednym cyklu są następujące, każdy prowadzony jest w zasadzie to samo z niewielkimi zmianami:
===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%)
Najszybszy podejście wydaje się być bezpośrednio dostępu do elementów w tablicy C za pomocą „* (carray + IDX)” , jednak najbardziej zastanawiające jest to, że przypisanie wskaźnika z tablicy C do obiektywnej zmiennej c "id object = * (carry + idx)" powoduje ogromną wydajność.
Początkowo sądziłem, że być może łuk robił coś z liczeniem referencji, ponieważ zmienna była silna, więc w tym momencie zmieniłem ją na słabą oczekując, że wydajność zwiększy "__weak id object = * (carry + idx)". Ku mojemu zdziwieniu było to znacznie wolniej.
Wyniki losowego dostępu poszły całkiem nieźle, jak się spodziewałem na podstawie wyników sekwencji, więc nie ma żadnych niespodzianek na szczęście.
W wyniku tego istnieje szereg pytań:
- Dlaczego przypisanie do zmiennej trwa tak długo?
- Dlaczego przypisywanie do słabej zmiennej trwa jeszcze dłużej? (Być może jest coś, czego nie rozumiem, co się dzieje tutaj)
- Biorąc pod uwagę powyższe, w jaki sposób Apple ma standardowe szybkie wyliczenie, aby tak dobrze działać?
I dla kompletności tutaj jest kod. Więc jestem tworzenia tablic w następujący sposób:
__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];
I NSTestObject jest zdefiniowany następująco:
@interface NSTestObject : NSObject
- (void)doSomething;
@end
@implementation NSTestObject
{
float f;
}
- (void)doSomething
{
f++;
}
A metoda stosowana do profilowania kodu:
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;
}
Wreszcie jest jak przeprowadzam aktualne testy:
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];
Wymagane czytanie: [Ryba z niedorzecznymi rybami: Array] (http://ridiculousfish.com/blog/posts/array.html) – Caleb