visual studio que new net mac dotmemory code c# .net linq performance resharper

c# - studio - ¿Preocupación de rendimiento cuando se usa LINQ “en todas partes”?



resharper visual studio code (9)

Después de actualizar a ReSharper5, me da consejos aún más útiles sobre mejoras de código. Una que veo en todas partes ahora es una sugerencia para reemplazar las declaraciones foreach con las consultas LINQ. Tomemos este ejemplo:

private Ninja FindNinjaById(int ninjaId) { foreach (var ninja in Ninjas) { if (ninja.Id == ninjaId) return ninja; } return null; }

Se sugiere reemplazar esto con lo siguiente usando LINQ:

private Ninja FindNinjaById(int ninjaId) { return Ninjas.FirstOrDefault(ninja => ninja.Id == ninjaId); }

Todo parece estar bien, y estoy seguro de que no hay problema con el rendimiento para reemplazar este para cada uno. ¿Pero es algo que debería hacer en general? ¿O podría tener problemas de rendimiento con todas estas consultas LINQ en todas partes?


Perfil


La única manera de saber con seguridad es hacer un perfil. Sí, ciertas consultas pueden ser más lentas. Pero cuando observa lo que ReSharper ha reemplazado aquí, es esencialmente lo mismo, hecho de una manera diferente. Los ninjas están en bucle, cada ID está marcada. En todo caso, podría argumentar que esta refactorización se reduce a la legibilidad. ¿Cuál de los dos encuentra más fácil de leer?

Los conjuntos de datos más grandes tendrán un impacto mayor, pero como he dicho, el perfil. Es la única manera de estar seguro de que tales mejoras tengan un efecto negativo.


Debe comprender lo que la consulta LINQ va a hacer "bajo el capó" y compararlo con la ejecución de su código antes de que pueda saber si debe cambiarlo. En general, no quiero decir que necesite saber el código exacto que se generará, pero sí necesita saber la idea básica de cómo se llevaría a cabo la operación. En su ejemplo, supongo que LINQ funcionaría básicamente de la misma manera que su código y como la declaración LINQ es más compacta y descriptiva, lo preferiría. Sin embargo, hay ocasiones en que LINQ puede no ser la opción ideal, aunque probablemente no muchas. En general, creo que casi cualquier construcción de bucle sería reemplazable por una construcción LINQ equivalente.


Hemos creado aplicaciones masivas, con LINQ rociado generosamente en todas partes. Nunca, nunca, nos ha ralentizado.

Es perfectamente posible escribir consultas LINQ que serán muy lentas, pero es más fácil corregir sentencias LINQ simples que enormes algoritmos para / if / for / return.

Sigue el consejo de Resharper :)


Lo anterior hace exactamente lo mismo.

Siempre que utilice sus consultas LINQ correctamente, no tendrá problemas de rendimiento. Si lo usa correctamente, es más probable que sea más rápido debido a la habilidad de las personas que crean LINQ.

Lo único que puede beneficiarse de crear el suyo propio es si desea un control total o LINQ no ofrece lo que necesita o si desea una mejor capacidad de depuración.


Lo bueno de las consultas LINQ es que hace que sea muy sencillo de convertir a una consulta paralela. Dependiendo de lo que estés haciendo, puede o no ser más rápido (como siempre, perfil), pero aún así es bastante ordenado.


Para agregar mi propia experiencia de usar LINQ donde el rendimiento realmente importa, con Monotouch, la diferencia sigue siendo insignificante.

Usted está ''discapacitado'' en el iPhone 3GS a alrededor de 46 mb de RAM y un procesador ARM de 620 mhz. Es cierto que el código es AOT compilado, pero incluso en el simulador donde está JIT y al pasar por una larga serie de direccionamiento indirecto, la diferencia es de décimas de milisegundo para conjuntos de miles de objetos.

Junto con Windows Mobile, aquí es donde debe preocuparse por los costos de rendimiento, no en las grandes aplicaciones ASP.NET que se ejecutan en servidores de cuatro núcleos de 8 gb o escritorios con puntajes dobles. Una excepción a esto sería con grandes conjuntos de objetos, aunque podría decirse que de todos modos sería perezoso cargar, y la tarea de consulta inicial se realizaría en el servidor de la base de datos.

Es un poco de un cliché en , pero use el código más corto y legible hasta que 100s de milisegundos realmente importen.


Permítanme comenzar diciendo que amo a LINQ por su expresividad y lo uso todo el tiempo sin ningún problema.

Sin embargo, hay algunas diferencias en el rendimiento. Normalmente son lo suficientemente pequeños como para ignorarlos, pero en la ruta crítica de su aplicación, puede haber ocasiones en que desee optimizarlos.

Aquí está el conjunto de diferencias que debe tener en cuenta, que podrían importar con el rendimiento:

  • LINQ usa excesivamente las llamadas de delegado, y las invocaciones de delegado son (un poquito muy pequeñas) más lentas que las invocaciones de métodos y, por supuesto, más lentas que el código en línea.
  • Un delegado es un puntero de método dentro de un objeto. Ese objeto necesita ser creado.
  • Los operadores LINQ usualmente devuelven un nuevo objeto (un iterador) que permite recorrer la colección en bucle. Los operadores LINQ encadenados crean así múltiples objetos nuevos.
  • Cuando su bucle interno utiliza objetos del exterior (llamados cierres) también tienen que estar envueltos en objetos (que deben crearse).
  • Muchos operadores de LINQ llaman al método GetEnumerator en una colección para iterarlo. La llamada a GetEnumerator generalmente garantiza la creación de otro objeto.
  • La iteración de la colección se realiza mediante la interfaz IEnumerator . Las llamadas de interfaz son un poco más lentas que las llamadas de método normales.
  • IEnumerator objetos de IEnumerator menudo necesitan ser eliminados o, al menos, Dispose tiene que ser llamado.

Cuando el rendimiento es una preocupación, también intente usar for over foreach .

Una vez más, me encanta LINQ y no recuerdo haber decidido nunca usar una consulta LINQ (a objetos) debido al rendimiento. Entonces, no hagas ninguna optimización prematura . Comience primero con la solución más legible, que optimice cuando sea necesario. Así que perfil, perfil y perfil .


Una anécdota: cuando estaba conociendo C # 3.0 y LINQ, todavía estaba en mi fase de "cuando tienes un martillo, todo parece un clavo". Como una tarea de la escuela, se suponía que escribiera un juego de cuatro / cuatro en línea como un ejercicio de algoritmos de búsqueda adversarial. Utilicé LINQ en todo el programa. En un caso particular, necesitaba encontrar la fila en la que caería una pieza de juego si la soltaba en una columna en particular. Perfecto caso de uso para una consulta LINQ! Esto resultó ser muy lento. Sin embargo, LINQ no era el problema, el problema era que, para empezar, estaba buscando . Optimicé esto simplemente manteniendo una tabla de consulta: una matriz de enteros que contiene el número de fila para cada columna del tablero de juego, actualizando esa tabla al insertar una pieza de juego. No hace falta decir que esto fue mucho, mucho más rápido.

Lección aprendida: primero optimice su algoritmo, y las construcciones de alto nivel como LINQ podrían hacerlo más fácil.

Dicho esto, hay un costo definido para la creación de todos esos delegados. Por otro lado, también puede haber un beneficio de rendimiento al utilizar la naturaleza perezosa de LINQ. Si recorres manualmente una colección, estás obligado a crear listas intermedias List<> mientras que con LINQ, básicamente transmites los resultados.


Una cosa que identificamos como problemática de rendimiento es crear muchos lambdas e iterar sobre pequeñas colecciones. ¿Qué sucede en la muestra convertida?

Ninjas.FirstOrDefault(ninja => ninja.Id == ninjaId)

Primero, se crea una nueva instancia de tipo de cierre (generado). Nueva instancia en montón gestionado, algunos trabajos para GC. En segundo lugar, se crea una nueva instancia de delegado a partir del método en ese cierre. Entonces se llama el método FirstOrDefault. ¿Que hace? Se itera la colección (igual que su código original) y llama al delegado.

Básicamente, tienes 4 cosas agregadas aquí: 1. Crear cierre 2. Crear delegado 3. Llamar a través de delegado 4. Reunir cierre y delegar

Si llama a FindNinjaById muchas veces, agregará esto para que sea un golpe de rendimiento importante. Por supuesto, mídelo.

Si lo reemplazas con (equivalente)

Ninjas.Where(ninja => ninja.Id == ninjaId).FirstOrDefault()

agrega 5. Creación de la máquina de estados para el iterador ("Where" es la función de rendimiento)