2010-05-27 10 views

Respuesta

18

Creo que "sustancial" es una exageración en casos de uso más.

Tener un constructor estático (incluso si no hace nada) afecta el tiempo de inicialización del tipo debido a la presencia/ausencia del beforefieldinit flag. Existen garantías más estrictas sobre el tiempo cuando tienes un constructor estático.

Para código más, yo sugeriría que esto no hace mucha diferencia, pero si estás haciendo un círculo cerrado y accediendo a un miembro estático de una clase, podría ser. Personalmente, no me preocuparía demasiado. Si sospecha que es relevante en su aplicación real, pruébela en lugar de adivinar. Microbenchmarks es muy probable que exageren el efecto aquí.

Vale la pena mencionar que .NET 4 behaves somewhat differently to previous versions cuando se trata de la inicialización de tipo, por lo que cualquier punto de referencia realmente debería mostrar las diferentes versiones para ser relevante.

11

Bueno, acabo de replicar su prueba.

Para 1000000000 iteraciones con un DEBUG acumulación consigo:

  • 4s para su clase estática con un constructor estático
  • 3.6s misma clase con comentada constructor estático
  • 2.9s con la clase no estática (y crear una instancia antes de la iteración ) con un constructor estático o no

Lo mismo con un LIBERACIÓN acumulación hace una diferencia más destacado:

  • clase estática con constructor estático: 4046.875ms
  • clase estática sin constructor estático: 484.375ms
  • Instancia constructor estático: 484.375ms
  • Instancia sin constructor estático: 484.375ms
+0

¿Qué versión de .NET estás usando, por interés? Supongo que se trata de una versión optimizada sin depuración que se ejecuta fuera del depurador. –

+0

3.5 SP1 - erm podría haber construido esto como una construcción de depuración realmente en retrospectiva ... permítanme intentarlo de nuevo con una versión de lanzamiento apropiada – Paolo

+0

@Jon - resultados actualizados, más intrigantes ... – Paolo

5

El CLR proporciona una garantía bastante sólida para la ejecución de constructores estáticos, promete llamarlos solo una vez y antes de se puede ejecutar cualquier método en la clase. Esa garantía es bastante difícil de implementar cuando hay múltiples hilos utilizando la clase.

Echando un vistazo al código fuente de CLR para SSCLI20, veo una gran cantidad de código dedicado a proporcionar esta garantía. Mantiene una lista de constructores estáticos en ejecución, protegidos por un bloqueo global. Una vez que obtiene una entrada en esa lista, cambia a un bloqueo específico de clase que asegura que no haya dos subprocesos que puedan ejecutar el constructor.Verificación doble de bloqueo en un bit de estado que indica que el constructor ya se ejecutó. Un montón de código inescrutable que ofrece garantías de excepción.

Bueno, este código no es gratis. Agréguelo al tiempo de ejecución para el cctor mismo y verá algo de sobrecarga. Como siempre, no dejes que esto afecte tu estilo, esta garantía también es muy buena y no querrías darte a ti mismo. Y mide antes de arreglarlo.

0

Acabo de hacer una pequeña prueba para comprobar el impacto de agregar un constructor estático a una de mis clases.

que tienen una clase base que tiene este aspecto:

public abstract class Base 
{ 
    public abstract Task DoStuffAsync(); 
} 

El problema es que en una de las puestas en práctica ese método no hace nada, para poder establecer una tarea completada pre-hechos y devolverlo cada hora.

public sealed class Test1 : Base 
{ 
    readonly Task _emptyTask; 

    public Test1() 
    { 
     TaskCompletionSource<Object> source = new TaskCompletionSource<object>(); 
     source.SetResult(null); 
     _emptyTask = source.Task; 
    } 

    public override Task DoStuffAsync() 
    { 
     return _emptyTask; 
    } 
} 

(Otra opción es volver a la tarea a la demanda pero resulta que este método es llamado siempre)

objetos de esta clase se crean muy, muy a menudo, por lo general en bucles. En cuanto a él, se ve como el establecimiento _emptyTask como un campo estático sería beneficioso ya que sería la misma Task para todos los métodos:

public sealed class Test2 : Base 
{ 
    static readonly Task _emptyTask; 

    static Test2() 
    { 
     TaskCompletionSource<Object> source = new TaskCompletionSource<object>(); 
     source.SetResult(null); 
     _emptyTask = source.Task; 
    } 

    public override Task DoStuffAsync() 
    { 
     return _emptyTask; 
    } 
} 

entonces recuerdo el "problema" con los constructores estáticos y rendimiento, y después de la investigación un poco (así es como he llegado hasta aquí), decido hacer una pequeña referencia:

Stopwatch sw = new Stopwatch(); 
List<Int64> test1list = new List<Int64>(), test2list = new List<Int64>(); 

for (int j = 0; j < 100; j++) 
{ 
    sw.Start(); 
    for (int i = 0; i < 1000000; i++) 
    { 
     Test1 t = new Test1(); 
     if (!t.DoStuffAsync().IsCompleted) 
      throw new Exception(); 
    } 
    sw.Stop(); 
    test1list.Add(sw.ElapsedMilliseconds); 

    sw.Reset(); 
    sw.Start(); 
    for (int i = 0; i < 1000000; i++) 
    { 
     Test2 t = new Test2(); 
     if (!t.DoStuffAsync().IsCompleted) 
      throw new Exception(); 
    } 
    sw.Stop(); 
    test2list.Add(sw.ElapsedMilliseconds); 

    sw.Reset(); 
    GC.Collect(); 
} 

Console.WriteLine("Test 1: " + test1list.Average().ToString() + "ms."); 
Console.WriteLine("Test 2: " + test2list.Average().ToString() + "ms."); 

y los resultados son bastante claro:

Test 1: 53.07 ms. 
Test 2: 5.03 ms. 
end 

Por lo tanto, a pesar de tener un constructor estático, el beneficio supera el problema. Entonces siempre mida.

+0

Lo sentimos, pero esta prueba no tiene sentido en su forma actual. En Test1 crea un nuevo TaskCompletionSource cada vez que crea una instancia de Test1. En Test2 solo creas uno en el constructor estático. Por lo tanto, Test1 tiene más trabajo por hacer y crea objetos adicionales que deben ser codificados, lo que probablemente sea la razón de las diferencias de tiempo. – Bunny83

Cuestiones relacionadas