2009-03-26 16 views
81

Tengo un programa que requiere un rendimiento rápido. Dentro de uno de sus bucles internos, necesito probar el tipo de un objeto para ver si hereda de cierta interfaz.C# 'es' el rendimiento del operador

Una forma de hacerlo sería con la funcionalidad incorporada de comprobación de tipos del CLR. El método más elegante que probablemente ser el 'es' palabra clave:

if (obj is ISpecialType) 

Otro enfoque sería dar la clase base de mi propia función GetType virtual() que devuelve un valor de enumeración predefinida (en mi caso, en realidad , solo necesito un bool). Ese método sería rápido, pero menos elegante.

He oído que hay una instrucción IL específicamente para la palabra clave 'es', pero eso no significa que se ejecuta rápidamente cuando se traduce al ensamblaje nativo. ¿Alguien puede compartir alguna información sobre el desempeño de "es" versus el otro método?

ACTUALIZACIÓN: Gracias por todas las respuestas informadas! Parece que hay un par de puntos útiles repartidos entre las respuestas: el punto de Andrew sobre "es" realizar automáticamente un reparto es esencial, pero los datos de rendimiento recopilados por Binary Worrier e Ian también son extremadamente útiles. Sería genial si se editara una de las respuestas para incluir todos de esta información.

+2

por cierto, CLR no le dará la posibilidad de crear su función propia Tipo GetType(), porque rompe una de las reglas principales CLR - verdaderamente tipos – abatishchev

+1

Er, no estoy completamente seguro de lo que quiere decir con la regla de "tipos verdaderos", pero entiendo que el CLR tiene una función GetType() tipo incorporada. Si tuviera que usar ese método, sería con una función de un nombre diferente que devuelva alguna enumeración, por lo que no habría ningún conflicto de nombre/símbolo. – JubJub

+3

Creo que abatishchev significaba "seguridad tipo". GetType() no es virtual para evitar que un tipo mienta sobre sí mismo y, por lo tanto, preserve la seguridad del tipo. –

Respuesta

99

El uso de is puede perjudicar el rendimiento si, una vez que verifica el tipo, lo transfiere a ese tipo. is realmente arroja el objeto al tipo que está verificando para que cualquier conversión posterior sea redundante.

Si se va a emitir todos modos, aquí es un mejor enfoque:

ISpecialType t = obj as ISpecialType; 

if (t != null) 
{ 
    // use t here 
} 
+0

Gracias. Pero si no voy a lanzar el objeto si el condicional falla, ¿sería mejor utilizar una función virtual para probar el tipo en su lugar? – JubJub

+4

@JubJub: no. Un 'como' fallido básicamente realiza la misma operación que 'is' (es decir, la comprobación de tipo). La única diferencia es que luego devuelve 'null' en lugar de' false'. –

15

Andrew es correcta. De hecho, con el análisis del código, Visual Studio lo informa como un molde innecesario.

Una idea (sin saber lo que estás haciendo es un poco oscura), pero siempre me han aconsejado que evite verificaciones como esta, y en cambio tener otra clase. Entonces, en lugar de hacer algunas comprobaciones y tener diferentes acciones según el tipo, haga que la clase sepa cómo procesarse a sí misma ...

p. Obj puede ser ISpecialType o IType;

ambos tienen un método DoStuff() definido. Para IType, puede simplemente devolver o hacer cosas personalizadas, mientras que ISpecialType puede hacer otras cosas.

Esto elimina por completo cualquier fundición, hace que el código sea más limpio y más fácil de mantener, y la clase sabe cómo hacer sus propias tareas.

+0

Sí, dado que todo lo que voy a hacer si las pruebas de tipo verdadero es llamar a un determinado método de interfaz, podría mover ese método de interfaz a la clase base y hacer que no haga nada por defecto. Eso podría ser más elegante que crear una función virtual para probar el tipo. – JubJub

+0

Hice una prueba similar a Binary Worrier después de los comentarios de abatishchev y encontré solo 60ms de diferencia sobre 10,000,000 iteraciones. – Ian

+1

Wow, gracias por la ayuda. Supongo que me limitaré a utilizar los operadores de comprobación de tipos por ahora, a menos que me parezca apropiado reorganizar la estructura de clases. Usaré el operador 'como' como sugirió Andrew, ya que no deseo realizar el envío de forma redundante. – JubJub

64

Estoy con Ian, probablemente no quiera hacer esto.

Sin embargo, para que lo sepas, hay muy poca diferencia entre los dos, más de 10.000.000 iteraciones

  • La verificación de enumeración está en el puesto milisegundos (aprox)
  • El es comprobar viene en al milisegundos (aprox)

personalmente no solucionar este problema º es el camino, pero si tuviera que elegir un método sería la verificación IS integrada, la diferencia de rendimiento no vale la pena considerar la sobrecarga de codificación.

Mi base y clases derivadas

class MyBaseClass 
{ 
    public enum ClassTypeEnum { A, B } 
    public ClassTypeEnum ClassType { get; protected set; } 
} 

class MyClassA : MyBaseClass 
{ 
    public MyClassA() 
    { 
     ClassType = MyBaseClass.ClassTypeEnum.A; 
    } 
} 
class MyClassB : MyBaseClass 
{ 
    public MyClassB() 
    { 
     ClassType = MyBaseClass.ClassTypeEnum.B; 
    } 
} 

JubJub: Conforme a lo solicitado más información sobre las pruebas.

que pasó las pruebas de una aplicación de consola (una versión de depuración) de cada prueba tiene el siguiente

static void IsTest() 
{ 
    DateTime start = DateTime.Now; 
    for (int i = 0; i < 10000000; i++) 
    { 
     MyBaseClass a; 
     if (i % 2 == 0) 
      a = new MyClassA(); 
     else 
      a = new MyClassB(); 
     bool b = a is MyClassB; 
    } 
    DateTime end = DateTime.Now; 
    Console.WriteLine("Is test {0} miliseconds", (end - start).TotalMilliseconds); 
} 

Correr en la liberación, me sale una diferencia de 60 - 70 ms, como Ian.

Además Actualización - Oct 2012 25to
Después de un par de años me di cuenta de algo acerca de esto, el compilador puede elegir omitir bool b = a is MyClassB en la liberación, porque b no se utiliza en cualquier lugar.

Este código. . .

public static void IsTest() 
{ 
    long total = 0; 
    var a = new MyClassA(); 
    var b = new MyClassB(); 
    var sw = new Stopwatch(); 
    sw.Start(); 
    for (int i = 0; i < 10000000; i++) 
    { 
     MyBaseClass baseRef; 
     if (i % 2 == 0) 
      baseRef = a;//new MyClassA(); 
     else 
      baseRef = b;// new MyClassB(); 
     //bool bo = baseRef is MyClassB; 
     bool bo = baseRef.ClassType == MyBaseClass.ClassTypeEnum.B; 
     if (bo) total += 1; 
    } 
    sw.Stop(); 
    Console.WriteLine("Is test {0} miliseconds {1}", sw.ElapsedMilliseconds, total); 
} 

. . . constantemente muestra el cheque is entrando a aproximadamente 57 milisegundos, y la comparación enum entrando en 29 milisegundos.

NByo todavía prefieren el cheque is, la diferencia es demasiado pequeña para preocuparse por

+28

+1 para realmente probar el rendimiento, en lugar de suponer. –

+0

¡Gracias por probar! Ian comentó anteriormente que realizó una prueba de rendimiento y que la diferencia entre los métodos fue aún más pequeña que la que encontraste. Tengo curiosidad de cómo llegaste a tus números. – JubJub

+0

¡Gracias por la actualización! Si entiendo correctamente, su segunda prueba invalida los resultados anteriores de 700 vs 1000 ms. Quizás debas actualizar la publicación para reflejar esto e incorporar la respuesta de Andrew para que esta sea la respuesta más informativa. – JubJub

20

Ok, así que estaba charlando de esto con alguien y decidió probar esto más. Por lo que puedo decir, el rendimiento de as y is son muy buenos, en comparación con la prueba de su propio miembro o función para almacenar información de tipo.

Utilicé Stopwatch, lo que acabo de aprender puede no ser el enfoque más confiable, así que también probé UtcNow. Más tarde, también probé el enfoque del tiempo del procesador, que parece similar al UtcNow, que incluye tiempos de creación impredecibles. También intenté hacer que la clase base no fuera abstracta sin virtuales, pero no pareció tener un efecto significativo.

Ejecuté esto en un Quad Q6600 con 16 GB de RAM. Incluso con iteraciones de 50 mil, los números todavía rebotan alrededor de +/- 50 o menos milisegundos, así que no leería demasiado sobre las pequeñas diferencias.

Fue interesante ver que x64 creado más rápido, pero ejecutado como/es más lento que el 86

64 Modo de disparo:
Cronómetro:
como: 561ms
es: propiedad 597ms
Base: campo de 539ms
Base: 555MS campo
Base RO: 552ms
GetEnumType virtual() prueba: 556ms
virtual ISB() Prueba: 588ms
Crear Hora: 10416ms

UtcNow:
como: 499ms
es: 532ms
propiedad Base: 479ms
campo Base: 502ms campo
Base RO: 491ms
GetEnumType virtual(): 502ms
ISB bool virtual(): 522ms
crear tiempo: 285ms (Este número parece poco fiable ingenio h UtcNow. También consigo 109ms y 806ms)

x 86 Modo de lanzamiento:.
Cronómetro:
como: 391ms
es: propiedad 423ms
Base: 369ms
campo Base: 321ms campo
Base RO : 339ms
Virtual GetEnumType() prueba: 361ms
Virtual IsB() prueba: 365ms
Crear tiempo: 14106ms

UtcNow:
como: 348ms
es: 375ms
propiedad Base: 329ms
campo Base: 286ms
Base RO campo: 309ms
GetEnumType virtual(): 321ms
virtual ISB bool (): 332ms
Crear tiempo: 544ms (Este número parece poco confiable con UtcNow.)

Aquí es la mayor parte del código:

static readonly int iterations = 50000000; 
    void IsTest() 
    { 
     Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)1; 
     MyBaseClass[] bases = new MyBaseClass[iterations]; 
     bool[] results1 = new bool[iterations]; 

     Stopwatch createTime = new Stopwatch(); 
     createTime.Start(); 
     DateTime createStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      if (i % 2 == 0) bases[i] = new MyClassA(); 
      else bases[i] = new MyClassB(); 
     } 
     DateTime createStop = DateTime.UtcNow; 
     createTime.Stop(); 


     Stopwatch isTimer = new Stopwatch(); 
     isTimer.Start(); 
     DateTime isStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i] is MyClassB; 
     } 
     DateTime isStop = DateTime.UtcNow; 
     isTimer.Stop(); 
     CheckResults(ref results1); 

     Stopwatch asTimer = new Stopwatch(); 
     asTimer.Start(); 
     DateTime asStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i] as MyClassB != null; 
     } 
     DateTime asStop = DateTime.UtcNow; 
     asTimer.Stop(); 
     CheckResults(ref results1); 

     Stopwatch baseMemberTime = new Stopwatch(); 
     baseMemberTime.Start(); 
     DateTime baseStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i].ClassType == MyBaseClass.ClassTypeEnum.B; 
     } 
     DateTime baseStop = DateTime.UtcNow; 
     baseMemberTime.Stop(); 
     CheckResults(ref results1); 

     Stopwatch baseFieldTime = new Stopwatch(); 
     baseFieldTime.Start(); 
     DateTime baseFieldStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B; 
     } 
     DateTime baseFieldStop = DateTime.UtcNow; 
     baseFieldTime.Stop(); 
     CheckResults(ref results1); 


     Stopwatch baseROFieldTime = new Stopwatch(); 
     baseROFieldTime.Start(); 
     DateTime baseROFieldStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B; 
     } 
     DateTime baseROFieldStop = DateTime.UtcNow; 
     baseROFieldTime.Stop(); 
     CheckResults(ref results1); 

     Stopwatch virtMethTime = new Stopwatch(); 
     virtMethTime.Start(); 
     DateTime virtStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i].GetClassType() == MyBaseClass.ClassTypeEnum.B; 
     } 
     DateTime virtStop = DateTime.UtcNow; 
     virtMethTime.Stop(); 
     CheckResults(ref results1); 

     Stopwatch virtMethBoolTime = new Stopwatch(); 
     virtMethBoolTime.Start(); 
     DateTime virtBoolStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i].IsB(); 
     } 
     DateTime virtBoolStop = DateTime.UtcNow; 
     virtMethBoolTime.Stop(); 
     CheckResults(ref results1); 


     asdf.Text += 
     "Stopwatch: " + Environment.NewLine 
      + "As: " + asTimer.ElapsedMilliseconds + "ms" + Environment.NewLine 
      +"Is: " + isTimer.ElapsedMilliseconds + "ms" + Environment.NewLine 
      + "Base property: " + baseMemberTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base field: " + baseFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base RO field: " + baseROFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType() test: " + virtMethTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual IsB() test: " + virtMethBoolTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Create Time : " + createTime.ElapsedMilliseconds + "ms" + Environment.NewLine + Environment.NewLine+"UtcNow: " + Environment.NewLine + "As: " + (asStop - asStart).Milliseconds + "ms" + Environment.NewLine + "Is: " + (isStop - isStart).Milliseconds + "ms" + Environment.NewLine + "Base property: " + (baseStop - baseStart).Milliseconds + "ms" + Environment.NewLine + "Base field: " + (baseFieldStop - baseFieldStart).Milliseconds + "ms" + Environment.NewLine + "Base RO field: " + (baseROFieldStop - baseROFieldStart).Milliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType(): " + (virtStop - virtStart).Milliseconds + "ms" + Environment.NewLine + "Virtual bool IsB(): " + (virtBoolStop - virtBoolStart).Milliseconds + "ms" + Environment.NewLine + "Create Time : " + (createStop-createStart).Milliseconds + "ms" + Environment.NewLine; 
    } 
} 

abstract class MyBaseClass 
{ 
    public enum ClassTypeEnum { A, B } 
    public ClassTypeEnum ClassType { get; protected set; } 
    public ClassTypeEnum ClassTypeField; 
    public readonly ClassTypeEnum ClassTypeReadonlyField; 
    public abstract ClassTypeEnum GetClassType(); 
    public abstract bool IsB(); 
    protected MyBaseClass(ClassTypeEnum kind) 
    { 
     ClassTypeReadonlyField = kind; 
    } 
} 

class MyClassA : MyBaseClass 
{ 
    public override bool IsB() { return false; } 
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.A; } 
    public MyClassA() : base(MyBaseClass.ClassTypeEnum.A) 
    { 
     ClassType = MyBaseClass.ClassTypeEnum.A; 
     ClassTypeField = MyBaseClass.ClassTypeEnum.A;    
    } 
} 
class MyClassB : MyBaseClass 
{ 
    public override bool IsB() { return true; } 
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.B; } 
    public MyClassB() : base(MyBaseClass.ClassTypeEnum.B) 
    { 
     ClassType = MyBaseClass.ClassTypeEnum.B; 
     ClassTypeField = MyBaseClass.ClassTypeEnum.B; 
    } 
} 
+30

(Algunos bonus 5 am- inspiraron a Shakespeare ...) Ser o no ser: esa es la pregunta: Si es más noble en el código sufrir Las enumeraciones y propiedades de bases abstractas, O para aprovechar las ofertas de un lingüista intermediario E invocando sus instrucciones, confíe en ellas? Adivinar: preguntarse; No más; y por un momento para discernir terminamos el dolor de cabeza y las mil preguntas subconscientes que los codificadores de tiempo limitado son herederos de. 'Es un cierre Devoutly to wish'd.Morir, no, sino dormir; Sí, voy a dormir, tal vez para soñar es y como en lo que puede derivarse de la base de la clase. –

+0

¿Podemos concluir que acceder a una propiedad es más rápido en x64 que acceder a un campo? ¿Por qué es una gran sorpresa para mí cómo puede ser esto? –

+0

No concluiría eso, porque: "Incluso con iteraciones de 50 mil, los números todavía rebotan alrededor de +/- 50 o menos milisegundos, así que no leería demasiado sobre las diferencias menores". –

13

hice un comparsion rendimiento en dos posibilidades de comparación de tipos

  1. myObject.GetType() == typeof (MiClase)
  2. myObject es MiClase

El resultado es: el uso de "es" es de aproximadamente 10 veces más rápido !!!

Salida:

Tiempo para el Tipo-Comparación: 00: 00: 00,456

Tiempo para Is-Comparación: 00: 00: 00,042

Mi Código:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Diagnostics; 

namespace ConsoleApplication3 
{ 
    class MyClass 
    { 
     double foo = 1.23; 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      MyClass myobj = new MyClass(); 
      int n = 10000000; 

      Stopwatch sw = Stopwatch.StartNew(); 

      for (int i = 0; i < n; i++) 
      { 
       bool b = myobj.GetType() == typeof(MyClass); 
      } 

      sw.Stop(); 
      Console.WriteLine("Time for Type-Comparison: " + GetElapsedString(sw)); 

      sw = Stopwatch.StartNew(); 

      for (int i = 0; i < n; i++) 
      { 
       bool b = myobj is MyClass; 
      } 

      sw.Stop(); 
      Console.WriteLine("Time for Is-Comparison: " + GetElapsedString(sw)); 
     } 

     public static string GetElapsedString(Stopwatch sw) 
     { 
      TimeSpan ts = sw.Elapsed; 
      return String.Format("{0:00}:{1:00}:{2:00}.{3:000}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds); 
     } 
    } 
} 
-3

Siempre me han aconsejado que evite las comprobaciones como esta, y Tead tiene otra clase. Entonces, en lugar de hacer algunas comprobaciones y tener diferentes acciones según el tipo, haga que la clase sepa cómo procesarse a sí misma ...

p. Obj puede ser ISpecialType o IType;

ambos tienen un método DoStuff() definido. Para IType, puede simplemente devolver o hacer cosas personalizadas, mientras que ISpecialType puede hacer otras cosas.

Esto elimina por completo cualquier fundición, hace que el código sea más limpio y más fácil de mantener, y la clase sabe cómo hacer sus propias tareas.

3

Point Andrew Hare hizo sobre el rendimiento perdido cuando se realiza is de verificación y luego fundido era válido, pero en C# 7.0 podemos hacer es comparar patrón de prueba de bruja para evitar elenco adicional más adelante:

if (obj is ISpecialType st) 
{ 
    //st is in scope here and can be used 
} 

Más aún si es necesario comprobar entre varios tipos de C# 7.0 construcciones de coincidencia de patrones ahora le permiten hacer switch en tipos:

public static double ComputeAreaModernSwitch(object shape) 
{ 
    switch (shape) 
    { 
     case Square s: 
      return s.Side * s.Side; 
     case Circle c: 
      return c.Radius * c.Radius * Math.PI; 
     case Rectangle r: 
      return r.Height * r.Length; 
     default: 
      throw new ArgumentException(
       message: "shape is not a recognized shape", 
       paramName: nameof(shape)); 
    } 
} 

puede leer más acerca de la coincidencia de patrones en C# en la documentación here .

Cuestiones relacionadas