Отсутствие оптимизации авторелиза под компилятор ARC

Мне просто интересно, почему в компиляторе ARC нет оптимизации пула автовыпуска, когда он сохраняет объект в самой внутренней области, удаляет его из пула авторелиза и освобождает, когда объект больше не используется?

Чтобы процитировать очень непрактичный пример из другого вопроса,

for(NSUInteger i = 0; i < 10000; i++)
{
    for(NSUInteger j = 0; j < 10000; j++)
    {
        NSNumber* n = [NSNumber numberWithUnsignedInteger:j];
        //NSLog(@"%@", n); //Disabled this to increase memory bloat faster.
    }
}

Без упаковки @autoreleasepool { ... } память растет и растет. Обертка с @autoreleasepool, памяти остается мало:

for(NSUInteger i = 0; i < 10000; i++)
{
    for(NSUInteger j = 0; j < 10000; j++)
    {
        @autoreleasepool {
            NSNumber* n = [NSNumber numberWithUnsignedInteger:j];
            //NSLog(@"%@", n); //Disabled this to increase memory bloat faster.
        }
    }
}

Но почему компилятор не может оптимизировать такие случаи, когда объекты не нужны за пределами самой внутренней области видимости, и устранить необходимость в обертке @autoreleasepool? Есть ли техническая причина, по которой это невозможно или еще не сделано?

Изменить

Чтобы уточнить, почему компилятор не может вывести код, подобный следующему:

for(NSUInteger i = 0; i < 10000; i++)
{
    for(NSUInteger j = 0; j < 10000; j++)
    {
        NSNumber* n = [NSNumber numberWithUnsignedInteger:j];
        objc_retain(n);
        objc_removeFromAutoreleasePool(n);
        NSLog(@"%@", n);
        objc_release(n);
    }
}

Изменить 2

По просьбе Грега вот результаты дизассемблирования обоих примеров выше.

Без @autoreleasepool { }:

TestOpt`-[LMViewController testAutoreleaseMem] at LMViewController.m:17:
0x2187:  pushl  %ebp
0x2188:  movl   %esp, %ebp
0x218a:  pushl  %ebx
0x218b:  pushl  %edi
0x218c:  pushl  %esi
0x218d:  subl   $0x1c, %esp
0x2190:  calll  0x2195                    ; -[LMViewController testAutoreleaseMem] + 14 at LMViewController.m:17
0x2195:  popl   %esi
0x2196:  xorl   %eax, %eax
0x2198:  movl   0x13cb(%esi), %ebx
0x219e:  movl   %eax, -0x10(%ebp)
0x21a1:  xorl   %edi, %edi
0x21a3:  movl   0x13df(%esi), %eax
0x21a9:  movl   %edi, 0x8(%esp)
0x21ad:  movl   %ebx, 0x4(%esp)
0x21b1:  movl   %eax, (%esp)
0x21b4:  calll  0x227e                    ; symbol stub for: objc_msgSend
0x21b9:  movl   %eax, (%esp)
0x21bc:  calll  0x2296                    ; symbol stub for: objc_retainAutoreleasedReturnValue
0x21c1:  movl   %eax, (%esp)
0x21c4:  calll  0x228a                    ; symbol stub for: objc_release
0x21c9:  incl   %edi
0x21ca:  cmpl   $0x2710, %edi
0x21d0:  jne    0x21a3                    ; -[LMViewController testAutoreleaseMem] + 28 at LMViewController.m:24
0x21d2:  movl   -0x10(%ebp), %eax
0x21d5:  incl   %eax
0x21d6:  cmpl   $0x2710, %eax
0x21db:  jne    0x219e                    ; -[LMViewController testAutoreleaseMem] + 23 at LMViewController.m:24
0x21dd:  addl   $0x1c, %esp
0x21e0:  popl   %esi
0x21e1:  popl   %edi
0x21e2:  popl   %ebx
0x21e3:  popl   %ebp
0x21e4:  ret    

С участием:

TestOpt`-[LMViewController testAutoreleaseMem] at LMViewController.m:17:
0x216f:  pushl  %ebp
0x2170:  movl   %esp, %ebp
0x2172:  pushl  %ebx
0x2173:  pushl  %edi
0x2174:  pushl  %esi
0x2175:  subl   $0x1c, %esp
0x2178:  calll  0x217d                    ; -[LMViewController testAutoreleaseMem] + 14 at LMViewController.m:17
0x217d:  popl   %ecx
0x217e:  movl   %ecx, -0x10(%ebp)
0x2181:  xorl   %eax, %eax
0x2183:  movl   0x13e3(%ecx), %ecx
0x2189:  movl   %eax, -0x14(%ebp)
0x218c:  xorl   %edi, %edi
0x218e:  movl   %ecx, %ebx
0x2190:  calll  0x2278                    ; symbol stub for: objc_autoreleasePoolPush
0x2195:  movl   %eax, %esi
0x2197:  movl   -0x10(%ebp), %eax
0x219a:  movl   0x13f7(%eax), %eax
0x21a0:  movl   %edi, 0x8(%esp)
0x21a4:  movl   %ebx, 0x4(%esp)
0x21a8:  movl   %eax, (%esp)
0x21ab:  calll  0x227e                    ; symbol stub for: objc_msgSend
0x21b0:  movl   %eax, (%esp)
0x21b3:  calll  0x2296                    ; symbol stub for: objc_retainAutoreleasedReturnValue
0x21b8:  movl   %eax, (%esp)
0x21bb:  calll  0x228a                    ; symbol stub for: objc_release
0x21c0:  movl   %esi, (%esp)
0x21c3:  calll  0x2272                    ; symbol stub for: objc_autoreleasePoolPop
0x21c8:  incl   %edi
0x21c9:  cmpl   $0x2710, %edi
0x21cf:  jne    0x2190                    ; -[LMViewController testAutoreleaseMem] + 33 at LMViewController.m:23
0x21d1:  movl   %ebx, %ecx
0x21d3:  movl   -0x14(%ebp), %eax
0x21d6:  incl   %eax
0x21d7:  cmpl   $0x2710, %eax
0x21dc:  jne    0x2189                    ; -[LMViewController testAutoreleaseMem] + 26 at LMViewController.m:24
0x21de:  addl   $0x1c, %esp
0x21e1:  popl   %esi
0x21e2:  popl   %edi
0x21e3:  popl   %ebx
0x21e4:  popl   %ebp
0x21e5:  ret    

person Leo Natan    schedule 16.01.2014    source источник
comment
если бы вы использовали alloc/initWith... у него была бы возможность вставить релиз... +numberWith всегда будет возвращать автоматически выпущенный объект. поэтому он будет сохранен пулом.   -  person Grady Player    schedule 17.01.2014
comment
@GradyPlayer Я знаю, как это работает сейчас. Я спрашиваю, почему объект не может быть удален из пула и поэтому не может быть выпущен после окончания области действия?   -  person Leo Natan    schedule 17.01.2014
comment
Я считаю чудом, что ARC вообще работает. Не будем выбирать гниды.   -  person Hot Licks    schedule 17.01.2014
comment
@HotLicks Ничего чудесного, просто отличная инженерия и хороший набор правил. Просто интересно, есть ли случаи, когда такую ​​оптимизацию сделать невозможно. Но то, что предлагает Роб, интересно.   -  person Leo Natan    schedule 17.01.2014


Ответы (3)


Любая операция «удалить из пула автовыпуска» будет неэффективной. Пул автоматического выпуска — это просто массив указателей для последующего выпуска. Не существует быстрого способа проверить наличие указателя в пуле.

В ARC есть оптимизация, которая может удалить автоматическое освобождение в некоторых случаях, когда вызываемый объект выполнял return [obj autorelease]. В моих тестах это уменьшает накладные расходы пула автоматического освобождения -[NSNumber numberWithUnsignedInteger:] до нуля.

Возможно, что некоторые версии OS X или iOS реализуют -numberWithUnsignedInteger: таким образом, что предотвращает оптимизацию автоматического возврата ARC. Также возможно, что некоторые версии компилятора не выполняют оптимизацию возврата-автоосвобождения, когда возвращаемый объект не используется.

В вашем первоначальном тесте внутренняя реализация NSLog() генерировала автоматически выпущенные объекты. ARC ничего не может с этим поделать, кроме как оборачивать каждый вызов функции или каждый цикл в пул автоматического освобождения.

person Greg Parker    schedule 16.01.2014
comment
Спасибо за Ваш ответ. Я фактически удалил вызов NSLog, чтобы память увеличивалась намного быстрее. Я тестировал Xcode 5.1b2 и iOS7.1SDK на x86_64. Как насчет автоматического переноса с пулом авторелиза? Каковы накладные расходы на push/pop пулов авторелиза? - person Leo Natan; 17.01.2014
comment
Пулы Autorelease относительно дешевы, но недостаточно дешевы, чтобы добавлять их повсюду. Можете ли вы опубликовать разбор сборки Release функции, которая просто запускает этот цикл? - person Greg Parker; 17.01.2014
comment
@LeoNatan Убедитесь, что вы тестируете с включенной оптимизацией, так как оптимизатор влияет на использование выпуска/сохранения/автоматического выпуска. Это может не повлиять на этот конкретный случай, но также может привести к значительному изменению вывода. - person bbum; 17.01.2014
comment
@bbum Оптимизация включена; уровень оптимизации: -Os - person Leo Natan; 17.01.2014

Почему вы считаете, что ARC не оптимизирует приведенный выше код? Попробуйте это в режиме Release в разделе «Инструменты». Куча не растет. Если вы тестируете под Debug, то проблема в том, что вы не задействуете оптимизатор.

person Rob Napier    schedule 16.01.2014
comment
В режиме деблокирования с оптимизацией -Os память продолжает увеличиваться. - person Leo Natan; 17.01.2014
comment
Можете ли вы опубликовать полную суть? Я профилировал это без увеличения памяти. gist.github.com/rnapier/8466934 - person Rob Napier; 17.01.2014

Вероятно, вы могли бы разбить функциональность на несколько вызовов, что дало бы коду время для дыхания. Например, когда метод выполняет x за раз, и когда этот метод возвращается, он, вероятно, освобождает эту память.

person 0xFADE    schedule 16.01.2014
comment
Это не ответ на вопрос и неверно в данном примере. Пример был просто приведен как средство быстрого увеличения памяти и демонстрации точки зрения. - person Leo Natan; 17.01.2014