我在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不是要执行的内容。 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也不同。

    关于c# - Stackoverflow在C#中进行拳击,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28865139/

    10-09 08:12