[ES] Rendimientos Linq vs For vs Foreach separando Strings

Hace algún tiempo en una entrevista de trabajo me propusieron una kata bastante interesante, consistía en que dado un archivo con un listado de personajes, separarlos en dos grupos, el patrón por el que debían ser separados era que aquellos denominados Villanos contendrían la letra d en su nombre.

Entre otras cosas querían evaluar el tratamiento de errores, y la efectividad de la separación por ejemplo. Bien, pues en esta segunda parte me encontré con algo realmente curioso, que lo que para alguien cómo yo acostumbrado a trabajar con linq lo más rápido de codificar suponía una deficiencia de rendimiento muy elevada desde un número bastante bajo de registros.

El planteamiento es el siguiente, con linq usaba un where y extraía todos los elementos que contenían la letra d de la siguiente manera, de forma que en una sola línea de código tenía todo el trabajo realizado:

El tiempo de ejecución para unos 1100 registros salía así: Tiempo de ejecución con Linq: 00:00:00.0060463

Sin duda es un tiempo ínfimo, que a pocos haría dudar de que hay formas más efectivas, una de esas personas era mi entrevistador, realizó el trabajo que yo debía haber realizado y me mostró que tanto con un bucle for cómo con un foreach el rendimiento era sensiblemente mejor, y más cuanto más grande fuera el archivo de datos.

El código de los dos bucles es muy similar:

La salida de estos dos bucles es sensiblemente mejor al de linq (he ajustado el funcionamiento porque hablando con Luis Guerrero (@GuerreroTook) me evidenció que estaba siendo injusto con Linq porque en sólo una pasada de los bucles conseguía héroes y villanos y sin embargo en linq tenía dos where: uno para los héroes y otro para los villanos, así que en los 3 casos sólo extraemos los villanos). Pero aún así la salida comparativa para 1100 registros es la siguiente:

Y la diferencia se vuelve más evidente cuando nos lanzamos a unos 50000 registros:

Y forzando la máquina con dos millones de registros:

¿Y esto que significa? Que no siempre lo más fácil de programar es lo más rápido y que si evaluamos la solución cómo la pedían en la kata en la que había que separar el listado inicial en dos en los que tenemos héroes y villanos, mientras los tiempos de los bucles for y foreach seguirían siendo siempre los mismos el timing para la ejecución con Linq pasaría a duplicarse. Los frameworks son nuestros gratos compañeros de viaje pero se deben usar siempre con cuidado y coherencia, sin olvidar que puede haber soluciones con bucles simples que pueden ser más efectivas.

PD: He intentado realizar también la comparación con expresiones regulares pero no he conseguido componer el patrón de búsqueda porque algunos de los nombres eran compuestos y no podía usar el espacio cómo separador. Si a alguien se le ocurre un patrón, el código está subido a github para todos aquellos que quieran probarlo

Actualización: Justo recién publicado este post di con la opción para realizarlo con una expresión regular. La preparación de los datos no entra en la evaluación de tiempo del bucle:

Acepto que el tema de la doble coma es un poco extraño, pero también que la expresión regular dado mi nivel no es del todo sencilla, veamos, quiero buscar todos los nombres (simples o compuestos) que contengan la letra d. Por tanto, necesito el espacio para separar los nombres y otro carácter cómo separador para la expresión regular. ¿Y porqué no usar sólo una coma? Bien, pues no puedo usar sólo una coma, porque la primera coma pertenece hace de cierre de la palabra y la segunda coma hace de apertura de la segunda, si pruebas el código en github “,Angel Dust,Dirk Anger,” esta cadena me reconoce con mi expresión regular sólo el primero de los elementos, cuando el segundo también debería cumplirla, de modo que mi expresión regular la dejé así: “,[^,]*(d)[^,]*,” que traducido a texto sería-> una coma, 0 o más de cualquier carácter salvo la coma, obligatoria una d, 0 o más de cualquier carácter salvo la coma y una coma de cierre.

Todo esto está muy bien, pero ¿no venías hablando de rendimiento? sí, efectivamente y aqui tenemos las comparativas.

mmmmm… sinceramente, los resultados arrojados por las expresiones regulares sólo me dicen una cosa -> que creo que no las estoy usando todo lo bien que debería, que encontré la forma de hacerlo pero que al no haber pulido la idea la ejecución se ha vuelto pobre y necesita varias vueltas más. Pero seguiré trabajando en ello.