2010-11-23 17 views
14

? He encontrado algunas reglas (recomendaciones) para usar concreto List y Dictionary en lugar de IList e IDictionary, dado que las pruebas de muestra muestran acceso a través de la interfaz. un poco más lento. Por ejemplo, agregar 10000 valores a una lista y luego hacer un conteo en la lista 1 billón de veces muestra que hacerlo a través de una interfaz es 28 veces más lento que hacerlo a través de la clase concreta. es decir, a través de la clase concreta se necesitan 80 ms, a través de la interfaz se tardan 2800 ms, lo que muestra lo lento que es a través de la interfaz. Dado esto, sería razonable usar la clase concreta. ¿Hay alguna razón por la cual la interfaz es mucho más lenta (probablemente más dirigida a alguien que sabe más sobre las funciones internas de .net).¿Es mejor para el rendimiento utilizar el tipo de hormigón en lugar de la interfaz

gracias scott

+0

Lo el lenguaje tiene un rendimiento tan miserable con las interfaces? – dicroce

+0

IList ilst = lst; for (int i = 0; i <10000; i ++) {lst.Add (i); } int count = 0; GC.Collect(); for (int i = 0; i <1000000000; i ++) {count = lst.Count; } – user455095

+1

Rara vez se verá una diferencia de rendimiento de un factor 100. Dudo la corrección de sus medidas de rendimiento. ¿Puedes mostrarnos tu código? – Steven

Respuesta

5

La razón principal para el uso de las interfaces es la flexibilidad, la separación de las preocupaciones etc.

Así que yo todavía consejos para utilizar interfaces en la mayoría de casos (no en todos, simplemente en su caso) y solo considere cambiar a las clases concretas cuando exista un problema de rendimiento real.

Y, después de ejecutar su punto de referencia sin el GC.Collect() y en el modo de Liberación, obtengo 96 y 560ms. Una diferencia mucho más pequeña.

3

Lo que tienes es un problema en su aplicación, interfaces se hacen estás código mejor y más fácil de reutilizar/mantener

Si realmente necesita para mejorar el rendimiento en un primer intento para mejorar el algoritmo, por ejemplo, ¿realmente necesita contar los elementos mil millones de tiempo? ¿no puedes almacenar el conteo en alguna parte y tener algún tipo de bandera que indique que los elementos han cambiado y necesitas volver a contarlos?

Dicho esto la cuestión en Performance impact of changing to generic interfaces adresses el rendimiento de las interfaces

+0

podríamos, pero supongo que es un caso de que es posible que cualquier código pueda terminar siendo ejecutado mil millones de veces (vale, eso suena un poco loco, pero es posible) y para evitarlo podrías decir que usas la clase concreta. Supongo que en este caso solo sería para listas y diccionarios en lugar de todas las interfaces posibles. – user455095

+1

Mi punto es que generalmente las mejoras de algoritmo pueden tener un gran efecto en el rendimiento (la eliminación del GC.Recopilar en su código es un buen ejemplo) haciendo las microptimizaciones innecesarias pero sí, usar clases concretas parece ser más rápido que usar interfaces – rpfaraco

5

Sus pruebas de rendimiento son claramente erróneas. Estás probando muchas cosas diferentes. En primer lugar, GC.Collect activa el hilo del finalizador, lo que afecta el rendimiento de todo lo que se ejecuta después. A continuación, no solo prueba la cantidad de tiempo que lleva invocar los métodos de interfaz, sino que dedica mucho tiempo a copiar datos en nuevas matrices (ya que está creando grandes matrices) y recopilarlas, por lo que la diferencia entre las llamadas a la interfaz y las llamadas a instancias se perderán totalmente.

Aquí hay una prueba en la que compruebo el rendimiento general de las llamadas a la interfaz. Cuando se ejecuta en modo de lanzamiento fuera de Visual Studio:

public interface IMyInterface 
{ 
    void InterfaceMethod(); 
} 

public class MyClass : IMyInterface 
{ 
    [MethodImpl(MethodImplOptions.NoInlining)] 
    public void InterfaceMethod() 
    { 
    } 

    [MethodImpl(MethodImplOptions.NoInlining)] 
    public void InstanceMethod() 
    { 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     // JITting everyting: 
     MyClass c = new MyClass(); 
     c.InstanceMethod(); 
     c.InterfaceMethod(); 
     TestInterface(c, 1); 
     TestConcrete(c, 1); 
     Stopwatch watch = Stopwatch.StartNew(); 
     watch.Start(); 
     var x = watch.ElapsedMilliseconds; 

     // Starting tests: 
     watch = Stopwatch.StartNew(); 

     TestInterface(c, Int32.MaxValue - 2); 

     var ms = watch.ElapsedMilliseconds; 

     Console.WriteLine("Interface: " + ms); 

     watch = Stopwatch.StartNew(); 

     TestConcrete(c, Int32.MaxValue - 2); 

     ms = watch.ElapsedMilliseconds; 

     Console.WriteLine("Concrete: " + ms); 
    } 

    static void TestInterface(IMyInterface iface, int iterations) 
    { 
     for (int i = 0; i < iterations; i++) 
     { 
      iface.InterfaceMethod(); 
     } 
    } 

    static void TestConcrete(MyClass c, int iterations) 
    { 
     for (int i = 0; i < iterations; i++) 
     { 
      c.InstanceMethod(); 
     } 
    } 
} 

Salida:

Interface: 4861 
Concrete: 4236 
+0

Esto acaba de tener una corrección realizada por el usuario: 1198075 que fue rechazado - después de la línea '// Comenzar las pruebas' el código llama a 'TestInterface' dos veces y no llama a' TestConcrete'. – qujck

+1

¿Por qué se rechazó la edición? Esta respuesta contiene un error y es engañosa. El resultado si TestInterface se compara con TestConcrete (y no consigo mismo) es: Interfaz: 5791 Concreto: 4470 –

+0

@JohanNilsson y qujck: De hecho, hubo un error en la prueba. Corregí la prueba y la salida. – Steven

2

Este es el código de prueba que estaba usando para ver las diferencias:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 

public class test1 
{ 
     static void Main(string[] args) 
     { 
     Stopwatch sw = new Stopwatch(); 

     const int NUM_ITEMS = 10000; 
     const int NUM_LOOPS2 = 1000000000; 
     List<int> lst = new List<int>(NUM_ITEMS); 
     IList<int> ilst = lst; 
     for (int i = 0; i < NUM_ITEMS; i++) 
     { 
      lst.Add(i); 
     } 
     int count = 0; 
     sw.Reset(); 
     //GC.Collect(); 
     sw.Start(); 
     for (int i = 0; i < NUM_LOOPS2; i++) 
     { 
      count = lst.Count; 
     } 
     sw.Stop(); 
     Console.Out.WriteLine("Took " + (sw.ElapsedMilliseconds) + "ms - 1."); 



     sw.Reset(); 
     //GC.Collect(); 
     sw.Start(); 
     for (int i = 0; i < NUM_LOOPS2; i++) 
     { 
      count = ilst.Count; 
     } 
     sw.Stop(); 
     Console.Out.WriteLine("Took " + (sw.ElapsedMilliseconds) + "ms - 2."); 


     } 
} 

en cuenta que la basura la colección no parece afectar esta prueba.

8

creo que es bastante obvio si nos fijamos en el desmontaje:

La versión IList se compila a:

  for (int i = 0; i < 1000000000; i++) 
0000003d xor   edi,edi 
      { 
       count = lst.Count; 
0000003f mov   ecx,esi 
00000041 call  dword ptr ds:[00280024h] 
00000047 mov   ebx,eax 
      for (int i = 0; i < 1000000000; i++) 
00000049 inc   edi 
0000004a cmp   edi,3B9ACA00h 
00000050 jl   0000003F 
      } 

El acceso a IList.Count se compila en una instrucción call.

La versión List por el contrario se colocarán en línea:

  for (int i = 0; i < 1000000000; i++) 
0000003a xor   edx,edx 
0000003c mov   eax,dword ptr [esi+0Ch] 
0000003f mov   esi,eax 
00000041 inc   edx 
00000042 cmp   edx,3B9ACA00h 
00000048 jl   0000003F 
      } 

Ninguna instrucción call aquí. Solo una instrucción mov, inc, cmp y jl en el ciclo. Por supuesto, esto es más rápido.

Pero recuerde: Generalmente, usted es haciendo algo con el contenido de su lista, no solo está iterando sobre él. Esto generalmente tomará mucho más tiempo que una llamada de función única, por lo que los métodos de interfaz de llamada raramente causarán problemas de rendimiento.

+0

gracias, esto ayuda a explicar las diferencias de perforación de todos modos y es cierto que lo que llevas en el ciclo llevaría más tiempo, pero supongo que es más como intentar optimizar cada línea de código en el ciclo para hacer que avance lo más rápido posible si la perforación es muy importante. – user455095

+0

+1 para mostrar IL. – Steven

+0

@Steven: en realidad, es montaje, no IL. Pero gracias de todos modos ;-) – Niki

3

No hubo una aparente diferencia de rendimiento en modo de depuración sin optimizar y alrededor del 35-40% de mejora en modo de lanzamiento con .Net 3.5, Visual Stusio 2008.

Debug: 
List test, ms: 1234.375 
IList test, ms: 1218.75 
Release: 
List test, ms: 609.375 
IList test, ms: 968.75 

Código de ensayo:

List<int> list = new List<int>(); 
var start = DateTime.Now; 
for (int i = 0; i < 50000000; i++) list.Add(i); 
for (int i = 0; i < 50000000; i++) list[i] = 0; 
var span = DateTime.Now - start; 
Console.WriteLine("List test, ms: {0}", span.TotalMilliseconds); 

IList<int> ilist = new List<int>(); 
start = DateTime.Now; 
for (int i = 0; i < 50000000; i++) ilist.Add(i); 
for (int i = 0; i < 50000000; i++) ilist[i] = 0; 
span = DateTime.Now - start; 
Console.WriteLine("IList test, ms: {0}", span.TotalMilliseconds); 
Cuestiones relacionadas