Stackoverflow haciendo boxeo en C#


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 recursivamente n veces (n > = 0) y en cada paso empuje 1000 enteros dentro de la pila creada.

Cuando corro el primer ejemplo con Foo(30000) no ocurre ninguna excepción, sin embargo el segundo ejemplo se bloquea con Foo(1000), solo n = 1000.

Cuando vi el CIL generado para ambos casos, la única diferencia fue la parte de boxeo para cada push:

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?

Author: Pang, 0000-00-00

1 answers

¿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 el número de las 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 muy alto, por lo que contar las instrucciones CIL no es una forma precisa de medir el "trabajo".

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

En el segundo caso, ya que estás usando una colección no genérica, cada llamada Push requiere que el entero sea encajonado, como determinaste en CIL.

Encajonar 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, entonces box it, que efectivamente también carga una referencia de objeto en la pila.

Si inspecciona esto 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 sugeriría el CIL generado.

La versión genérica efectivamente compila como una serie de llamadas así:

0000022c  nop 
            S.Push(2
 59
Author: ,
Warning: date() expects parameter 2 to be long, string given in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61