2009-08-19 13 views
5

Esto es más una pregunta académica sobre el rendimiento que un "qué debería usar" realista, pero tengo curiosidad ya que no hago mucho en IL para ver qué está construido y no tengo un gran conjunto de datos en mano al perfil contra.¿Qué es más rápido en un bucle: llamar una propiedad dos veces o almacenar la propiedad una vez?

Así que es más rápido:

List<myObject> objs = SomeHowGetList(); 
List<string> strings = new List<string>(); 
foreach (MyObject o in objs) 
{ 
    if (o.Field == "something") 
     strings.Add(o.Field); 
} 

o:

List<myObject> objs = SomeHowGetList(); 
List<string> strings = new List<string>(); 
string s; 
foreach (MyObject o in objs) 
{ 
    s = o.Field; 
    if (s == "something") 
     strings.Add(s); 
} 

tener en cuenta que en realidad no quiero saber el impacto en el rendimiento de la string.Add (s) (como cualquier operación que necesite hacerse realmente no se puede cambiar), solo la diferencia de rendimiento entre la configuración s cada iteración (digamos que s puede ser cualquier tipo primitivo o cadena) versos que llaman al getter sobre el objeto en cada iteración.

+19

¿Por qué preguntas _us_? Usted ha escrito el código. Saca un cronómetro, ejecútalo mil millones de veces en ambas direcciones, y luego sabrás cuál es más rápido. –

Respuesta

9

Su primera opción es notablemente más rápida en mis pruebas. ¡Soy como flip flopper!En serio, algunos comentarios se hicieron sobre el código en mi prueba original. Aquí está el código actualizado que muestra que la opción 2 es más rápida.

class Foo 
    { 
     public string Bar { get; set; } 

     public static List<Foo> FooMeUp() 
     { 
      var foos = new List<Foo>(); 

      for (int i = 0; i < 10000000; i++) 
      { 
       foos.Add(new Foo() { Bar = (i % 2 == 0) ? "something" : i.ToString() }); 
      } 

      return foos; 
     } 
    } 

    static void Main(string[] args) 
    { 

     var foos = Foo.FooMeUp(); 
     var strings = new List<string>(); 

     Stopwatch sw = Stopwatch.StartNew(); 

     foreach (Foo o in foos) 
     { 
      if (o.Bar == "something") 
      { 
       strings.Add(o.Bar); 
      } 
     } 

     sw.Stop(); 
     Console.WriteLine("It took {0}", sw.ElapsedMilliseconds); 

     strings.Clear(); 
     sw = Stopwatch.StartNew(); 

     foreach (Foo o in foos) 
     { 
      var s = o.Bar; 
      if (s == "something") 
      { 
       strings.Add(s); 
      } 
     } 

     sw.Stop(); 
     Console.WriteLine("It took {0}", sw.ElapsedMilliseconds); 
     Console.ReadLine(); 
    } 
+0

¡Hola, +1 para escribir una prueba! Te daría +2 si pudiera ... –

+0

Andy: ¿Qué plataforma/tiempo de ejecución probaste? Además, ¿es una versión de lanzamiento? –

+0

Para mí, el resultado es: 2294, 702 que contradice su conclusión. –

7

La mayoría de las veces, su segundo fragmento de código debe ser al menos tan rápido como el primer fragmento.

Estos dos fragmentos de código no son funcionalmente equivalentes. No se garantiza que las propiedades devuelvan el mismo resultado en los accesos individuales. Como consecuencia, el optimizador JIT no puede almacenar en caché el resultado (excepto en casos triviales) y será más rápido si guarda en caché el resultado de una propiedad de larga ejecución. Mira este ejemplo: why foreach is faster than for loop while reading richtextbox lines.

Sin embargo, para algunos casos específicos como:

for (int i = 0; i < myArray.Length; ++i) 

donde myArray es un objeto de matriz, el compilador es capaz de detectar el patrón y optimizar el código y omitir los controles consolidados. Podría ser más lento si guardar en caché el resultado de Length propiedad como:

int len = myArray.Length; 
for (int i = 0; i < myArray.Length; ++i) 
+0

Suponiendo que esto compila, el o.Field en ambos casos será del tipo string. También estoy asumiendo que el valor de o.Field, para cada objeto en la colección, se establece en algún valor significativo. Tal vez no entiendo muy bien a qué te refieres; ¿Podría ser más específico? –

+0

Revisa el enlace en mi respuesta actualizada. Es un ejemplo específico en el que hace una gran diferencia si guarda en caché el valor de retorno. –

2

Almacenar el valor en un campo es la opción más rápida.

Aunque una llamada a método no impone una gran sobrecarga, supera con creces el almacenamiento del valor una vez a una variable local en la pila y luego recuperarlo.

Por mi parte, lo hago constantemente.

2

Generalmente, el segundo es más rápido, ya que el primero recalcula la propiedad en cada iteración. Aquí es un ejemplo de algo que podría tener cantidad significativa de tiempo:

var d = new DriveInfo("C:"); 
d.VolumeLabel; // will fetch drive label on each call 
4

Realmente depende de la implementación. En la mayoría de los casos, se asume (como una práctica común/cortesía) que una propiedad no es costosa. Sin embargo, podría ocurrir que cada "get" realice una búsqueda no almacenada en caché sobre algún recurso remoto. Para propiedades simples y estándar, nunca notará una diferencia real entre los dos. En el peor de los casos, buscar una vez, almacenar y reutilizar será mucho más rápido.

Estaría tentado de usar get dos veces hasta que sepa que hay un problema ... "optimización prematura", etc ... Pero; si lo estuviera usando en un ciclo cerrado, , entonces Podría almacenarlo en una variable. Excepto por Length en una matriz, que tiene tratamiento JIT especial ;-p

+0

@Marc: ¿No es el problema con el primer fragmento de código que 'o.Field' en realidad podría cambiar el valor entre probarlo contra" algo "y agregarlo a la' Lista'? 'o.Field ==" something "' podría evaluar true, pero para el momento en que llamas a 'strings.Add' estás agregando" something else "? –

+0

@Grant - oh absolutamente podría, pero de nuevo sería ... no estándar - o al menos, debería estar bien documentado. Si es por enhebrarlo, entonces solo nosotros tenemos la culpa, por supuesto. –

+0

@Marc: No digo que sea una optimización prematura, especialmente cuando se trata de propiedades estúpidas que no son O (1) (muchas de ellas existen en WinForms) como la que he vinculado en mi respuesta. Además, en escenarios multiproceso, es posible que prefiera mantener los resultados en aras de la corrección. –

Cuestiones relacionadas