2012-07-13 21 views
7

me di cuenta de que la puesta en marcha veces variaría dependiendo de aquí coloqué un trozo de código de inicialización. Pensé que esto era realmente extraño, así que escribí un pequeño punto de referencia, que confirmó mis sospechas. Parece que el código que se ejecuta antes de invocar el método principal es más lento de lo normal.Código de constructor estático funciona más lento

¿Por qué Benchmark(); ejecutarse a diferentes velocidades dependiendo de si se llama antes y después de la ruta de código regular?

Aquí está el código de referencia:

class Program { 
    static Stopwatch stopwatch = new Stopwatch(); 
    static Program program = new Program(); 

    static void Main() { 
     Console.WriteLine("main method:"); 
     Benchmark(); 
     Console.WriteLine(); 

     new Program(); 
    } 

    static Program() { 
     Console.WriteLine("static constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    public Program() { 
     Console.WriteLine("public constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    static void Benchmark() { 
     for (int t = 0; t < 5; t++) { 
      stopwatch.Reset(); 
      stopwatch.Start(); 
      for (int i = 0; i < 1000000; i++) 
       IsPrime(2 * i + 1); 
      stopwatch.Stop(); 
      Console.WriteLine(stopwatch.ElapsedMilliseconds + " ms"); 
     } 
    } 

    static Boolean IsPrime(int x) { 
     if ((x & 1) == 0) 
      return x == 2; 
     if (x < 2) 
      return false; 
     for (int i = 3, s = (int)Math.Sqrt(x); i <= s; i += 2) 
      if (x % i == 0) 
       return false; 
     return true; 
    } 
} 

Los resultados muestran que Benchmark() carreras casi dos veces más lenta, tanto para el constructor estático y constructor de la static Program program propiedad:

// static Program program = new Program() 
public constructor: 
894 ms 
895 ms 
887 ms 
884 ms 
883 ms 

static constructor: 
880 ms 
872 ms 
876 ms 
876 ms 
872 ms 

main method: 
426 ms 
428 ms 
426 ms 
426 ms 
426 ms 

// new Program() in Main() 
public constructor: 
426 ms 
427 ms 
426 ms 
426 ms 
426 ms 

Duplicar el número de iteraciones en el ciclo de referencia hace que todos los tiempos se dupliquen, lo que sugiere que la penalización de rendimiento incurrida no es constante, sino un factor.

// static Program program = new Program() 
public constructor: 
2039 ms 
2024 ms 
2020 ms 
2019 ms 
2013 ms 

static constructor: 
2019 ms 
2028 ms 
2019 ms 
2021 ms 
2020 ms 

main method: 
1120 ms 
1120 ms 
1119 ms 
1120 ms 
1120 ms 

// new Program() in Main() 
public constructor: 
1120 ms 
1128 ms 
1124 ms 
1120 ms 
1122 ms 

¿Por qué sería este el caso? Tendría sentido si la inicialización fuera igual de rápida si se hiciera donde pertenece. Las pruebas se realizaron en .NET 4, modo de lanzamiento, optimizaciones en.

+2

¿Cuál es la pregunta exactamente? – jcolebrand

+0

¿Configuraciones de compilación? ¿Versión del marco? – user7116

+0

Por favor, compruebe si mis ediciones tienen sentido (no tengo ni idea de por qué), intenté con .Net 4/release con resultados similares. –

Respuesta

3

Este es un tema muy interesante. Pasé algún tiempo experimentando con variantes de tu programa. Aquí hay algunas observaciones:

  1. Si se mueve el punto de referencia() método estático en una clase diferente, la penalización de rendimiento para el constructor estático desaparece.

  2. Si realiza el método de referencia() en un método de instancia, la penalización de rendimiento desaparece.

  3. Cuando perfilo sus casos rápidos (1, 2) y sus casos lentos (3, 4), los casos lentos pasaron el tiempo adicional en los métodos de ayuda de CLR, en particular JIT_GetSharedNonGCStaticBase_Helper.

En base a esta información, puedo especular sobre lo que está sucediendo. El CLR necesita asegurarse de que cada constructor estático se ejecute a lo sumo una vez. Una complicación es que los constructores estáticos pueden formar un ciclo (por ejemplo, si la clase A contiene un campo estático de tipo B y la clase B contiene un campo estático de tipo A).

Cuando se ejecuta dentro de un constructor estático, la entrada del compilador JIT comprueba algunas llamadas a métodos estáticos para evitar posibles ciclos infinitos debido a las dependencias de clases cíclicas. Una vez que se llama al método estático desde fuera de un constructor estático, el CLR vuelve a compilar el método para eliminar las comprobaciones.

Esto debería estar muy cerca de lo que está sucediendo.

+0

Gracias por mirar el problema más, especialmente el perfilado. Aunque no estoy seguro acerca de su análisis; implicaría una carga constante en mis pruebas. He actualizado mi pregunta para mostrar que la penalización de rendimiento parece ser un factor. – Zong

+0

Hay una sobrecarga adicional ** por cada llamada IsPrime() ** porque cada llamada IsPrime tiene que estar rodeada de cheques. Por ejemplo, si hace que IsPrime sea más caro (por ejemplo, llame a IsPrime (1000000000 + 2 * i)), la diferencia entre los cuatro casos desaparece. –

+0

Es decir, si duplica el número de iteraciones, también duplica el número de llamadas IsPrime y, en consecuencia, duplica el número de comprobaciones realizadas. –

3

Este es un hecho muy bien documentado.

Los constructores estáticos son lentos. El tiempo de ejecución .net no es lo suficientemente inteligente como para optimizarlos.

se refieren: Performance penalty of static constructors

constructores estáticos explícitos son caros porque requieren que el tiempo de ejecución para asegurar que el valor se establece exactamente antes se accede a cualquier miembro de la clase. El costo exacto es depende del escenario, pero puede ser bastante notable en algunos casos.

+0

No creo que se aplique el artículo vinculado. El artículo dice que un tipo con un constructor estático está marcado antes de FieldInit y, en consecuencia, trabajar con ese tipo implica un costo adicional de tiempo de ejecución. Sin embargo, OP no está comparando dos tipos, uno con un constructor estático y otro sin él. Solo hay un tipo en el benchmark completo y ese tipo tiene un constructor estático. Entonces, algo más está pasando. –

+0

Tienes razón; Mi punto de referencia realmente sugiere que el rendimiento de los dos casos en el artículo es más o menos equivalente. – Zong

Cuestiones relacionadas