Недавно я провел небольшой исследовательский проект по обеспечению последовательного или произвольного доступа к NSArray по отношению к массиву C. Большинство тестовых случаев появляются, как я ожидал бы, однако некоторые из них не работают, как я думал, они будут, и я надеюсь, что кто-то может объяснить, почему.
В основном тест состоит из заполнения массива C объектами 50k, итерации по каждому из них и вызова метода (который внутренне просто увеличивает приращение в объекте), вторая часть теста включает создание цикла, который завершает итерации 50k но обращается к случайному объекту в массиве. В основном это довольно просто.
Чтобы выполнить сравнение, я инициализирую NSArray с помощью C-массива. Затем каждый тест выполняется через блок, переданный в метод, который отслеживает время, необходимое для выполнения блока. Код, который я использую, содержится ниже, но я хотел бы охватить результаты и запросы, которые у меня есть.
Эти тесты выполнялись на iPhone 4 и завершались в dispatch_after, чтобы смягчить любые оставшиеся потоковые или неатомные операции в результате запуска приложения. Результаты одного прогона следующие, каждый прогон по существу тот же, что и с небольшими вариациями:
===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 с использованием "* (carray + idx)", что является самым озадачивающим, хотя это назначение указателя из массива C объективной переменной c "id object = * (carry + idx)" приводит к огромному результату.
Сначала я понял, что возможно, что-то делать с подсчетом ссылок, поскольку переменная была сильной, поэтому на этом этапе я изменил ее на слабые, ожидая, что производительность увеличится "__weak id object = * (carry + idx)". К моему удивлению, это было на самом деле намного медленнее.
Результаты случайного доступа прошли довольно хорошо, как я ожидал, основываясь на результатах последовательности, так что сюрпризов там не достаточно.
В результате этого возникает ряд вопросов:
- Почему присвоение переменной занимает так много времени?
- Почему присвоение слабой переменной занимает еще больше времени? (Может быть, я не понимаю, что здесь происходит)
- Учитывая вышеизложенное, как Apple получила стандартное быстрое перечисление, чтобы так хорошо работать?
И для полноты здесь есть код. Поэтому я создаю массивы следующим образом:
__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];
И NSTestObject определяется следующим образом:
@interface NSTestObject : NSObject
- (void)doSomething;
@end
@implementation NSTestObject
{
float f;
}
- (void)doSomething
{
f++;
}
И метод, используемый для профилирования кода:
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;
}
Наконец, вот как я выполняю фактические тесты:
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];