Липса на оптимизация за автоматично освобождаване под 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. Какво ще кажете за автоматично обвиване с пул за автоматично освобождаване? Какви са режийните разходи за натискане/изскачане на пулове за автоматично освобождаване? - person Leo Natan; 17.01.2014
comment
Пуловете за автоматично освобождаване са сравнително евтини, но не достатъчно евтини, за да ги добавяте навсякъде. Можете ли да публикувате разглобяването на Release build на функция, която просто изпълнява този цикъл? - person Greg Parker; 17.01.2014
comment
@LeoNatan Уверете се, че тествате с активирана оптимизация, тъй като оптимизаторът влияе върху използването на освобождаване/запазване/автоматично освобождаване. Това може да не повлияе на този конкретен случай, но може също да доведе до значително различен резултат. - person bbum; 17.01.2014
comment
@bbum Оптимизациите са активирани; ниво на оптимизация: -Os - person Leo Natan; 17.01.2014

Защо смятате, че ARC не оптимизира горния код? Опитайте в режим на освобождаване под инструменти. Купчината не расте. Ако тествате под 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