Уменьшите пиковое использование памяти с помощью @autoreleasepool
Я работаю над приложением для iPad, которое имеет процесс синхронизации, использующий веб-службы и основные данные в узком цикле. Чтобы уменьшить объем памяти в соответствии с рекомендациямиApple , я периодически выделяю и сливаю NSAutoreleasePool
. Это в настоящее время работает отлично, и нет никаких проблем с памятью с текущим приложением. Однако я планирую перейти на ARC, где NSAutoreleasePool
больше не действует, и хотел бы сохранить этот же вид производительности. Я создал несколько примеров и приурочил их и Я задаюсь вопросом, каков наилучший подход, используя ARC, для достижения той же производительности и поддержания читаемости кода .
Для тестирования я придумал 3 сценария, каждый из которых создает строку, используя число от 1 до 10 000 000. Я пробежал каждый пример 3 раза, чтобы определить, сколько времени они заняли, используя 64-битное приложение Mac с компилятором Apple LLVM 3.0 (w/o gdb-O0) и XCode 4.2. Я также прогнал каждый пример через инструменты, чтобы примерно увидеть, что пик памяти был.
Каждый из приведенных ниже примеров содержится в следующем блоке кода:int main (int argc, const char * argv[])
{
@autoreleasepool {
NSDate *now = [NSDate date];
//Code Example ...
NSTimeInterval interval = [now timeIntervalSinceNow];
printf("Duration: %fn", interval);
}
}
Nsautoreleasepool Batch [Original Pre-ARC] (Пиковая память: ~116 КБ)
static const NSUInteger BATCH_SIZE = 1500;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for(uint32_t count = 0; count < MAX_ALLOCATIONS; count++)
{
NSString *text = [NSString stringWithFormat:@"%u", count + 1U];
[text class];
if((count + 1) % BATCH_SIZE == 0)
{
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
}
}
[pool drain];
Время Выполнения:
10.928158
10.912849
11.084716
Outer @autoreleasepool (Пиковая память: ~382 МБ)
@autoreleasepool {
for(uint32_t count = 0; count < MAX_ALLOCATIONS; count++)
{
NSString *text = [NSString stringWithFormat:@"%u", count + 1U];
[text class];
}
}
Время Выполнения:
11.489350
11.310462
11.344662
Inner @autoreleasepool (Пиковая память: ~61,2 КБ)
for(uint32_t count = 0; count < MAX_ALLOCATIONS; count++)
{
@autoreleasepool {
NSString *text = [NSString stringWithFormat:@"%u", count + 1U];
[text class];
}
}
Время Выполнения:
14.031112
14.284014
14.099625
@autoreleasepool w / goto (Пиковая память: ~115 КБ)
static const NSUInteger BATCH_SIZE = 1500;
uint32_t count = 0;
next_batch:
@autoreleasepool {
for(;count < MAX_ALLOCATIONS; count++)
{
NSString *text = [NSString stringWithFormat:@"%u", count + 1U];
[text class];
if((count + 1) % BATCH_SIZE == 0)
{
count++; //Increment count manually
goto next_batch;
}
}
}
Время Выполнения:
10.908756
10.960189
11.018382
Оператор goto
предложил наиболее близкую производительность, но он использует goto
. Есть какие-нибудь мысли?
Обновление:
Примечание: оператор goto
является обычным выходом для @ autoreleasepool, как указано в документации , и не будет пропускать память.
При входе запускается пул авторелиза. На обычном выходе (перерыв, возвращение, Гото, проваливаются, и так далее) в autorelease пул выталкивается. Для совместимости с существующим кодом, если выход вызван исключением, в autorelease пул не хлопнуть.
2 ответа:
Следующее должно достичь того же, что и ответ
goto
без ответаgoto
:for (NSUInteger count = 0; count < MAX_ALLOCATIONS;) { @autoreleasepool { for (NSUInteger j = 0; j < BATCH_SIZE && count < MAX_ALLOCATIONS; j++, count++) { NSString *text = [NSString stringWithFormat:@"%u", count + 1U]; [text class]; } } }
Обратите внимание, что ARC позволяет выполнять значительные оптимизации, которые не включены в
-O0
. Если вы собираетесь измерять производительность в ARC, вы должны тестировать с включенной оптимизацией. В противном случае вы будете измерять свое настроенное вручную размещение удержания/высвобождения против "наивного режима"ARC.Снова запустите тесты с оптимизациями и посмотрите, что произойдет.
Update : мне было любопытно, поэтому я запустил его сам. Это результаты выполнения в режиме выпуска (- Os), с 7,000,000 распределения.
arc-perf[43645:f803] outer: 8.1259 arc-perf[43645:f803] outer: 8.2089 arc-perf[43645:f803] outer: 9.1104 arc-perf[43645:f803] inner: 8.4817 arc-perf[43645:f803] inner: 8.3687 arc-perf[43645:f803] inner: 8.5470 arc-perf[43645:f803] withGoto: 7.6133 arc-perf[43645:f803] withGoto: 7.7465 arc-perf[43645:f803] withGoto: 7.7007 arc-perf[43645:f803] non-ARC: 7.3443 arc-perf[43645:f803] non-ARC: 7.3188 arc-perf[43645:f803] non-ARC: 7.3098
И пики памяти (работают только со 100 000 выделений, потому что инструменты занимают вечность):
Эти результаты меня немного удивляют. Ну, пиковые результаты памяти-нет; это именно то, что вы ожидаете. Но разница во времени выполнения междуOuter: 2.55 MB Inner: 723 KB withGoto: ~747 KB Non-ARC: ~748 KB
inner
иwithGoto
, даже с включенной оптимизацией, выше, чем я ожидал бы. Конечно, это своего рода патологический Микротест, который очень маловероятен для моделирования реального мира. производительность любого приложения. Вывод здесь заключается в том, что ARC действительно может иметь некоторое количество накладных расходов, но вы всегда должны измерять свое фактическое применение, прежде чем делать предположения.(Кроме того, я проверил ответ @ipmcc, используя вложенные циклы for; он вел себя почти точно так же, как версия
goto
.)