2014-01-16 59 views
1

我只是想知道,爲什麼在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  
+0

如果您使用了alloc/initWith ...它將有機會插入發佈...+ numberWith將始終返回一個自動釋放對象。所以它會被游泳池保留。 –

+0

@GradyPlayer我知道它是如何工作的。我在問爲什麼不能將該對象從池中刪除,因此一旦範圍結束就會被釋放? –

+1

ARC認爲這是一個奇蹟。我們不要選尼特。 –

回答

2

任何「從自動釋放池中刪除」操作都是低效的。一個自動釋放池只是一個稍後發佈的指針數組。沒有快速的方法來檢查池中是否存在指針。

ARC有一個優化,可以從被調用者執行的某些情況下刪除autorelease return [obj autorelease]。在我的測試中,這減少了 - [NSNumber numberWithUnsignedInteger:]的自動釋放池開銷爲零。

某些版本的OS X或iOS可能會實施-numberWithUnsignedInteger:以防止ARC的返回autorelease優化的方式。當返回的對象未被使用時,某些編譯器版本也可能無法執行return-autorelease優化。

在您的原始測試中,NSLog()的內部實現生成了自動釋放對象。 ARC沒有什麼能夠做到這一點,它包裝了每個函數調用或autorelease池中的每個循環。

+0

感謝您的回答。我實際上刪除了'NSLog'調用來增加內存的速度。我使用x86_64上的Xcode 5.1b2和iOS7.1SDK進行了測試。 自動換行autorelease池怎麼樣?什麼是推動/流行的autorelease池的開銷? –

+0

自動釋放池相對便宜,但並不便宜,無法將它們添加到任何地方。你可以發佈一個剛剛運行該循環的函數的Release版本的反彙編嗎? –

+0

請參閱編輯問題。 –

2

爲什麼你認爲ARC不優化上面的代碼?在「樂器」下以發行模式嘗試。堆不增長。如果您在Debug下進行測試,那麼問題在於您沒有參與優化器。

+0

在釋放模式下,使用'-Os'優化,內存不斷增加。 –

+0

你能發表一個完整的要點嗎?我沒有任何內存增長的情況下進行了描述。 https://gist.github.com/rnapier/8466934 –

0

您可能會將功能分解爲多個呼叫,這會使代碼有時間喘息。就像有一個方法一次做x一樣,當這個方法返回時它可能會釋放這個內存。

+0

這不是問題的答案,在此示例中不正確。這個例子只是作爲快速增加記憶力和證明一個觀點的手段而給出的。 –