domingo, 6 de junio de 2010

String Format más bonito pero más lento....

Antes, cuando escribíamos nuestro código en general lo hacíamos pensando en que funcionara como se debe, al menos esa era la "fe". Ahora sabemos que existen una serie de buenas prácticas que debemos seguir, lo cierto es que esas buenas practicas aveces se toman como una tautología y las utilizamos sin siquiera cuestionar si aplican o no a nuestro proyecto.

Sabemos que en .Net los objetos string son inmutables, lo que quiere decir que una vez creados "nunca" se podrán cambiar. Al hacer concatenaciones con el operador + lo que hace el runtime es "abandonar" el viejo string y crear uno nuevo, esperando que el garbage collector pase a recoger esa memoria sin referencia que dejamos tirada. Para evitar esto usamos ya sea los métodos Join, Concat, Format, entre otros, o, utilizamos la clase StringBuilder.

El método string.Format ha tomado mucha popularidad ya que es muy útil, entre otras cosas, permite aplicar localización, simplifica el formateo y facilita la lectura del código. Es por ello que muchos han comenzado a abandonar las clásicas concatenaciones, sin detenerse a pensar que hay detrás de esté método tan útil. Pero ¿Será más lento?

La respuesta es sí, string.Format es más lento que las concatenación. Para demostrarlo hice un pequeño test en el cual se itera 1000000 veces la misma prueba, una con string.Format y otra usando concatenaciones con el operador +.

   1:  private double PruebaConcat()
   2:  {
   3:      DateTime inicioConcat = DateTime.Now;
   4:      for (int i = 0; i < 1000000; i++)
   5:      {
   6:          string s = "Test suma i = " + i + " Mas i * 2 " + i * 2;
   7:      }
   8:      DateTime finConcat = DateTime.Now;
   9:      return finConcat.Subtract(inicioConcat).TotalMilliseconds;
  10:  }
  11:  

  12:  private double PruebaFormat()
  13:  {
  14:      DateTime inicioFormat = DateTime.Now;
  15:      for (int i = 0; i < 1000000; i++)
  16:      {
  17:          string s = string.Format("Suma i {0} Mas i * 2 {1}", i, i * 2);
  18:      }
  19:      DateTime finFormat = DateTime.Now;
  20:      return finFormat.Subtract(inicioFormat).TotalMilliseconds;
  21:  }
  22:  

  23:  private double[] CorrerPruebas(Func<double> test)
  24:  {
  25:      double[] arrayTest = new double[numeroPruebas];
  26:      for (int pruebas = 0; pruebas < numeroPruebas; pruebas++)
  27:      {
  28:          arrayTest[pruebas] = test();
  29:      }
  30:      return arrayTest;
  31:  }

La prueba se corre 100 veces y los resultados se dan en mili segundos:

Nota:
  • La diferencia es simplemente la resta del menor dato al mayor. Con algún software como Fathom se calcula fácilmente la diferencia de promedios para dos poblaciones independientes cuando la varianzas son diferentes. Incluso a "pata" es simple, pero siendo sincero me ganó el sueño.
  • Los cálculos fueron hechos con el .Net Framework 4 ya que las implementaciones pueden variar entre las versiones del Framework.
Estadísticamente los datos no representan mucho, ni son muy confiables por las condiciones, pero si nos dan una idea de las diferencias.
En la prueba se ve como las concatenaciones son más eficientes. Si bien un promedio de 298 mili segundos no parece significativo, es algo muy relativo al proyecto.

Algunas alternativas podrían ser: crear nuestro propio método Format (usando métodos de extensión sería más elegante) o saber diferenciar cuando usar cada uno de los estilos (para ello encontré este bonito post en code project ). Un ejemplo aparte del rendimiento donde las concatenaciones nos ayudan es que aceptan nulo mientras que Format lanza una excepción, pero este ya es un hecho meramente de diseño.

Es probable que este overhead no tenga mucha incidencia en la mayoría de nuestros proyectos, pero nos ayuda a pensar en la necesidad de revisar con detalle las buenas prácticas que aplicamos y en más esencialmente que metemos en nuestro código.

2 comentarios:

CR3364N dijo...

Ya que esta haciendo benchmark de ambos metodos debiera tambien agregar StringBuilder, deacuerdo a la teoria este metodo debiera ser mas eficiente que la concatenacion, y por ende mas eficiente que el String.Format (el cual no sabia yo que era mas lento :S lo que se viene a enterar uno).

Unknown dijo...

Claro, gracias por el comment, voy a versi le hago un update con StringBuilder, algo había leído, que si era más rápido cuando eran muchos datos.