visual una tipo studio stackoverflowexception produjo net excepción excepcion error controlada c# stack-overflow il

una - Stackoverflow haciendo boxeo en C#



system stackoverflowexception vb net (1)

¿Por qué, si no hay una sobrecarga significativa de la pila de CIL para el segundo ejemplo, se bloquea "más rápido" que el primero?

Tenga en cuenta que la cantidad de instrucciones CIL no representa con precisión la cantidad de trabajo o memoria que se utilizará. Una sola instrucción puede tener un impacto muy bajo o un impacto muy alto, por lo que contar las instrucciones CIL no es una forma precisa de medir el "trabajo".

También se dan cuenta de que el CIL no es lo que se ejecuta. El JIT compila el CIL a las instrucciones de la máquina real, con una fase de optimización, por lo que el CIL puede ser muy diferente a las instrucciones ejecutadas reales.

En el segundo caso, ya que está utilizando una colección no genérica, cada llamada Push requiere que el entero esté en una casilla, como determinó en CIL.

El boxeo de un entero crea efectivamente un objeto que "envuelve" el Int32 para usted. En lugar de simplemente cargar un entero de 32 bits en la pila, ahora tiene que cargar un entero de 32 bits en la pila, luego encuadrarlo, lo que efectivamente también carga una referencia de objeto en la pila.

Si lo inspecciona en la ventana de Desmontaje, puede ver que la diferencia entre la versión genérica y no genérica es dramática, y mucho más significativa de lo que sugiere el CIL generado.

La versión genérica se compila efectivamente como una serie de llamadas como tal:

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);

El no genérico, por otro lado, tiene que crear los objetos en caja, y en su lugar compila para:

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

Aquí se puede ver el significado del boxeo.

En su caso, boxear el número entero hace que las referencias de objetos en caja se carguen en la pila. En mi sistema, esto está provocando un flujo de pila en cualquier llamada más grande que Foo(127) (en 32 bits), lo que sugiere que los números enteros y las referencias de objetos en caja (4 bytes cada uno) se mantienen en la pila, como 127 * 1000 * 8 == 1016000, que está peligrosamente cerca del tamaño predeterminado de pila de subprocesos de 1 MB para aplicaciones .NET.

Cuando se usa la versión genérica, ya que no hay ningún objeto en caja, los enteros no tienen que estar todos almacenados en la pila y se está reutilizando el mismo registro. Esto le permite recibir una cantidad significativamente mayor (> 40000 en mi sistema) antes de usar la pila.

Tenga en cuenta que esto dependerá de la versión CLR y de la plataforma, ya que también hay un JIT diferente en x86 / x64.

Tengo estos dos trozos de código en C #:

primero

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 ); } }

Segundo

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 ); } }

Ambos hacen lo mismo:

  1. Cree una pila (genérica en <int> para el primer ejemplo y una pila de objeto para el segundo).

  2. Declare un método que se llama a sí mismo recursivamente n veces (n> = 0) y en cada paso, introduzca 1000 enteros dentro de la pila creada.

Cuando ejecuto el primer ejemplo con Foo(30000) no se produce ninguna excepción, sin embargo, el segundo ejemplo falla con Foo(1000) , solo n = 1000.

Cuando vi el CIL generado para ambos casos, la única diferencia era la parte del boxeo por cada empuje:

primero

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

Segundo

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

Mi pregunta es: ¿Por qué, si no hay una sobrecarga significativa de la pila de CIL para el segundo ejemplo, se bloquea "más rápido" que el primero?