2008-12-09 7 views
34

Tenemos muchas llamadas de inicio de sesión en nuestra aplicación. Nuestro registrador toma un parámetro System.Type para que pueda mostrar qué componente creó la llamada. A veces, cuando nos molesta, hacemos algo como:Rendimiento de Object.GetType()

class Foo 
{ 
    private static readonly Type myType = typeof(Foo); 

    void SomeMethod() 
    { 
    Logger.Log(myType, "SomeMethod started..."); 
    } 
} 

Como esto requiere obtener el objeto Tipo solo una vez. Sin embargo, no tenemos ninguna métrica real sobre esto. Alguien tiene alguna idea de cuánto ahorra por llamar a this.GetType() cada vez que inicia sesión?

(me di cuenta que podía hacer las métricas a mí mismo sin gran problema, pero bueno, lo que es stackoverflow para?)

Respuesta

68

tengo la fuerte sospecha de que GetType() tomará mucho menos tiempo que cualquier registro real. Por supuesto, existe la posibilidad de que su llamada a Logger.Log no haga ningún IO real ... Aun así, sospecho que la diferencia será irrelevante.

EDITAR: El código de referencia está en la parte inferior. Resultados:

typeof(Test): 2756ms 
TestType (field): 1175ms 
test.GetType(): 3734ms 

que está llamando el método 100 millones de veces - las ganancias de optimización de un par de segundos más o menos. Sospecho que el método de registro real tendrá mucho más trabajo que hacer, y llamar a eso 100 millones de veces tomará mucho más de 4 segundos en total, incluso si no escribe nada. (Podría estar equivocado, por supuesto, tendrías que intentarlo tú mismo).

En otras palabras, como es normal, elegiría el código más legible en lugar de la micro-optimización.

using System; 
using System.Diagnostics; 
using System.Runtime.CompilerServices; 

class Test 
{ 
    const int Iterations = 100000000; 

    private static readonly Type TestType = typeof(Test); 

    static void Main() 
    { 
     int total = 0; 
     // Make sure it's JIT-compiled 
     Log(typeof(Test)); 

     Stopwatch sw = Stopwatch.StartNew(); 
     for (int i = 0; i < Iterations; i++) 
     { 
      total += Log(typeof(Test)); 
     } 
     sw.Stop(); 
     Console.WriteLine("typeof(Test): {0}ms", sw.ElapsedMilliseconds); 

     sw = Stopwatch.StartNew(); 
     for (int i = 0; i < Iterations; i++) 
     { 
      total += Log(TestType); 
     } 
     sw.Stop(); 
     Console.WriteLine("TestType (field): {0}ms", sw.ElapsedMilliseconds); 

     Test test = new Test(); 
     sw = Stopwatch.StartNew(); 
     for (int i = 0; i < Iterations; i++) 
     { 
      total += Log(test.GetType()); 
     } 
     sw.Stop(); 
     Console.WriteLine("test.GetType(): {0}ms", sw.ElapsedMilliseconds); 
    } 

    // I suspect your real Log method won't be inlined, 
    // so let's mimic that here 
    [MethodImpl(MethodImplOptions.NoInlining)] 
    static int Log(Type type) 
    { 
     return 1; 
    } 
} 
+0

Interesante que el índice de referencia de Sam Meldrum a continuación da una diferencia aún menos significativa ... – Gaz

+0

Dado que en mi Eee PC (apenas una potencia) su prueba se ejecuta en solo ~ 2500 ms, supongo que está ejecutando una versión de depuración o ejecutándose debajo del depurador Sin embargo, están haciendo cosas ligeramente diferentes de todos modos. Observe la alineación, etc. –

+0

Y en realidad, su prueba es falsa de todos modos, acaba de notar un error. Agregará un comentario –

15

La función GetType() está marcado con el atributo especial [MethodImpl(MethodImplOptions.InternalCall)]. Esto significa que su cuerpo de método no contiene IL, sino que es un gancho en las partes internas de .NET CLR. En este caso, mira la estructura binaria de los metadatos del objeto y construye un objeto System.Type a su alrededor.

EDIT: supongo que estaba equivocado acerca de algo ...

me dijeron que: "porque GetType() requiere un nuevo objeto que se va a construir" pero parece que esto no es correcto. De alguna manera, el CLR almacena en caché el Type y siempre devuelve el mismo objeto para que no necesite construir un nuevo objeto Type.

Estoy basa en la siguiente prueba:

Object o1 = new Object(); 
Type t1 = o1.GetType(); 
Type t2 = o1.GetType(); 
if (object.ReferenceEquals(t1,t2)) 
    Console.WriteLine("same reference"); 

Por lo tanto, no espero gran ganancia en su aplicación.

+3

¿Qué te hace pensar que está creando un objeto nuevo cada vez? De hecho, es trivial mostrar que no es un caso. Imprimir object.ReferenceEquals (x.GetType(), x.GetType()). –

+2

:) Lo hice incluso antes de escribir este comentario y corregí mi respuesta. Gracias. –

7

Dudo que va a obtener una respuesta satisfactoria de SO sobre este tema. La razón es que el rendimiento, especialmente los escenarios de este tipo, son altamente específicos de la aplicación.

Alguien puede publicar con un ejemplo rápido de cronómetro que sería más rápido en términos de milisegundos sin procesar. Pero, francamente, eso no significa nada para su aplicación. ¿Por qué? Depende en gran medida del patrón de uso en ese escenario particular. Por ejemplo ...

  1. ¿Cuántos tipos tiene?
  2. ¿Qué tan grande son sus métodos?
  3. ¿Lo hace para cada método, o solo para los grandes?

Estas son solo algunas de las preguntas que alterarán en gran medida la relevancia de un punto de referencia en tiempo real.

+1

+1. Agregue el hecho de que los micro benchmarks pueden salirse con el bloat de código (variables adicionales) sin crear fallas de caché, mientras que en una aplicación real pueden crear caché de valores, presión de gc, falta de caché, paginación ... los micro benchmarks son irrelevantes. El perfilado es el camino a seguir. –

+0

Estoy de acuerdo en que las microbenchmarks se deben usar con cuidado, pero creo que en este caso demuestran que la llamada a GetType() es bastante barata, por lo que es poco probable que la micro-optimización ayude.Sin embargo, tenga en cuenta que este era un campo estático: no hay mucha presión GC adicional allí ... –

+0

Nota adicional: la creación de perfiles cambiaría el rendimiento de la aplicación * mucho * más que la diferencia entre llamar a GetType() y no. Es una operación naturalmente invasiva. El benchmarking específico de la aplicación es la mejor manera de hacerlo. Si no puede demostrar que una optimización ayuda * significativamente * en la aplicación ... –

2

La diferencia es probablemente insignificante en lo que respecta al rendimiento de la aplicación. Pero el primer enfoque donde almacena en caché el tipo debe ser más rápido. Vamos a probar.

Este código se mostrará la diferencia:

using System; 

namespace ConsoleApplicationTest { 
    class Program { 
     static void Main(string[] args) { 

      int loopCount = 100000000; 

      System.Diagnostics.Stopwatch timer1 = new System.Diagnostics.Stopwatch(); 
      timer1.Start(); 
      Foo foo = new Foo(); 
      for (int i = 0; i < loopCount; i++) { 
       bar.SomeMethod(); 
      } 
      timer1.Stop(); 
      Console.WriteLine(timer1.ElapsedMilliseconds); 

      System.Diagnostics.Stopwatch timer2 = new System.Diagnostics.Stopwatch(); 
      timer2.Start(); 
      Bar bar = new Bar(); 
      for (int i = 0; i < loopCount; i++) { 
       foo.SomeMethod(); 
      } 
      timer2.Stop(); 
      Console.WriteLine(timer2.ElapsedMilliseconds); 

      Console.ReadLine(); 
     } 
    } 

    public class Bar { 
     public void SomeMethod() { 
      Logger.Log(this.GetType(), "SomeMethod started..."); 
     } 
    } 

    public class Foo { 
     private static readonly Type myType = typeof(Foo); 
     public void SomeMethod() { 
      Logger.Log(myType, "SomeMethod started..."); 
     } 
    } 

    public class Logger { 
     public static void Log(Type type, string text) { 
     } 
    } 
} 

En mi máquina, lo que dio resultados de aprox. 1500 milisegundos para el primer acercamiento y aprox. 2200 milisegundos para el segundo.

(código y los tiempos corregidos - DOH)

+1

Intenta cambiar el segundo foo.SomeMethod() a bar.SomeMethod(). Actualmente estás evaluando lo mismo dos veces :) –

+0

Gracias Jon - ¡demasiado apresurado! ¡Do! –

0

utilizando el campo es la mejor manera y evitar bloqueo interno diccionario causando por typeof() y GetType() para mantener referencia único.