[英]Stackoverflow doing boxing in C#
我在C#中有這兩個代碼塊:
class Program
{
static Stack<int> S = new Stack<int>();
static int Foo(int n) {
if (n == 0)
return 0;
S.Push(0);
S.Push(1);
...
S.Push(999);
return Foo( n-1 );
}
}
class Program
{
static Stack S = new Stack();
static int Foo(int n) {
if (n == 0)
return 0;
S.Push(0);
S.Push(1);
...
S.Push(999);
return Foo( n-1 );
}
}
他們都這樣做:
創建一個堆棧(第一個示例的<int>
通用,第二個示例的一個對象堆棧)。
聲明一個遞歸調用n次(n> = 0)的方法,並在每個步驟中在創建的堆棧內推送1000個整數。
當我用Foo(30000)
運行第一個例子時,沒有異常發生,但是第二個例子與Foo(1000)
崩潰,只有n = 1000。
當我看到為兩種情況生成的CIL時 ,唯一的區別是每次推送的拳擊部分:
IL_0030: ldsfld class [System]System.Collections.Generic.Stack`1<int32> Test.Program::S
IL_0035: ldc.i4 0x3e7
IL_003a: callvirt instance void class [System]System.Collections.Generic.Stack`1<int32>::Push(!0)
IL_003f: nop
IL_003a: ldsfld class [mscorlib]System.Collections.Stack Test.Program::S
IL_003f: ldc.i4 0x3e7
IL_0044: box [mscorlib]System.Int32
IL_0049: callvirt instance void [mscorlib]System.Collections.Stack::Push(object)
IL_004e: nop
我的問題是:為什么,如果第二個例子的CIL堆棧沒有明顯的重載,它是否比第一個例子“更快”崩潰?
為什么,如果第二個例子的CIL堆棧沒有明顯的重載,它會比第一個“更快”崩潰嗎?
請注意,CIL指令的數量並不能准確表示將使用的工作量或內存量。 單個指令的影響可能非常小或影響很大,因此計算CIL指令並不是衡量“工作”的准確方法。
還要意識到CIL不是被執行的。 JIT將CIL編譯為具有優化階段的實際機器指令,因此CIL可能與實際執行的指令非常不同。
在第二種情況下,由於您使用的是非泛型集合,因此每次Push
調用都需要將整數裝箱,就像在CIL中確定的那樣。
裝箱整數有效地創建了一個“包裝” Int32
的對象。 它現在必須將32位整數加載到堆棧上,然后將其裝箱,而不是僅僅將32位整數加載到堆棧上,這樣就可以有效地將對象引用加載到堆棧中。
如果在“反匯編”窗口中對此進行檢查,則可以看到通用版本與非通用版本之間的差異是顯着的,並且比生成的CIL建議的更為重要。
通用版本有效地編譯為一系列調用,如下所示:
0000022c nop
S.Push(25);
0000022d mov ecx,dword ptr ds:[03834978h]
00000233 mov edx,19h
00000238 cmp dword ptr [ecx],ecx
0000023a call 71618DD0
0000023f nop
S.Push(26);
00000240 mov ecx,dword ptr ds:[03834978h]
00000246 mov edx,1Ah
0000024b cmp dword ptr [ecx],ecx
0000024d call 71618DD0
00000252 nop
S.Push(27);
另一方面,非泛型必須創建盒裝對象,而是編譯為:
00000645 nop
S.Push(25);
00000646 mov ecx,7326560Ch
0000064b call FAAC20B0
00000650 mov dword ptr [ebp-48h],eax
00000653 mov eax,dword ptr ds:[03AF4978h]
00000658 mov dword ptr [ebp+FFFFFEE8h],eax
0000065e mov eax,dword ptr [ebp-48h]
00000661 mov dword ptr [eax+4],19h
00000668 mov eax,dword ptr [ebp-48h]
0000066b mov dword ptr [ebp+FFFFFEE4h],eax
00000671 mov ecx,dword ptr [ebp+FFFFFEE8h]
00000677 mov edx,dword ptr [ebp+FFFFFEE4h]
0000067d mov eax,dword ptr [ecx]
0000067f mov eax,dword ptr [eax+2Ch]
00000682 call dword ptr [eax+18h]
00000685 nop
S.Push(26);
00000686 mov ecx,7326560Ch
0000068b call FAAC20B0
00000690 mov dword ptr [ebp-48h],eax
00000693 mov eax,dword ptr ds:[03AF4978h]
00000698 mov dword ptr [ebp+FFFFFEE0h],eax
0000069e mov eax,dword ptr [ebp-48h]
000006a1 mov dword ptr [eax+4],1Ah
000006a8 mov eax,dword ptr [ebp-48h]
000006ab mov dword ptr [ebp+FFFFFEDCh],eax
000006b1 mov ecx,dword ptr [ebp+FFFFFEE0h]
000006b7 mov edx,dword ptr [ebp+FFFFFEDCh]
000006bd mov eax,dword ptr [ecx]
000006bf mov eax,dword ptr [eax+2Ch]
000006c2 call dword ptr [eax+18h]
000006c5 nop
在這里你可以看到拳擊的意義。
在您的情況下,裝箱整數會導致裝箱對象引用加載到堆棧中。 在我的系統上,這會導致任何大於Foo(127)
(32位)的調用的堆棧溢出,這表明整數和盒裝對象引用(每個4個字節)都保留在堆棧中,如127 * 1000 * 8 == 1016000,危險地接近.NET應用程序的默認1 MB線程堆棧大小。
使用通用版本時,由於沒有盒裝對象,因此整數不必全部存儲在堆棧中,並且正在重用相同的寄存器。 這使您可以在用完堆棧之前顯着增加(在我的系統上> 40000)。
請注意,這將取決於CLR版本和平台,因為x86 / x64上還有不同的JIT。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.