Parece que la optimización es un arte perdido en estos días. ¿No hubo un momento en que todos los programadores exprimían hasta la última gota de eficiencia de su código? ¿Lo hace a menudo mientras camina cinco millas en la nieve?
En el espíritu de traer de vuelta un arte perdido, ¿cuáles son algunos consejos que conozca para cambios simples (o quizás complejos) para optimizar el código C # / .NET? Dado que es algo tan amplio que depende de lo que uno esté tratando de lograr, ayudaría a proporcionar contexto con su consejo. Por ejemplo:
- Al concatenar muchas cadenas, utilice
StringBuilder
en su lugar. Consulte el enlace en la parte inferior para conocer las advertencias al respecto. - Úselo
string.Compare
para comparar dos cadenas en lugar de hacer algo comostring1.ToLower() == string2.ToLower()
El consenso general que hasta ahora parece estar midiendo es clave. Este tipo de no entiende el punto: medir no le dice qué está mal, o qué hacer al respecto si se encuentra con un cuello de botella. Una vez me encontré con el cuello de botella de la concatenación de cadenas y no tenía idea de qué hacer al respecto, por lo que estos consejos son útiles.
Mi punto para publicar esto es tener un lugar para los cuellos de botella comunes y cómo pueden evitarse incluso antes de toparse con ellos. Ni siquiera se trata necesariamente de un código plug and play que cualquiera deba seguir ciegamente, sino más bien de comprender que se debe pensar en el rendimiento, al menos un poco, y que hay algunos errores comunes a los que hay que prestar atención.
Sin embargo, puedo ver que podría ser útil saber también por qué es útil una sugerencia y dónde debe aplicarse. Para el StringBuilder
consejo, encontré la ayuda que hice hace mucho tiempo aquí en el sitio de Jon Skeet .
fuente
Respuestas:
Hubo una vez al día en que la fabricación de, digamos, microscopios se practicaba como un arte. Los principios ópticos se entendieron mal. No hubo estandarización de piezas. Los tubos, engranajes y lentes tenían que ser hechos a mano por trabajadores altamente calificados.
En estos días, los microscopios se producen como una disciplina de ingeniería. Los principios subyacentes de la física se comprenden muy bien, las piezas listas para usar están ampliamente disponibles y los ingenieros de construcción de microscopios pueden tomar decisiones informadas sobre cómo optimizar mejor su instrumento para las tareas para las que está diseñado.
Que el análisis de la interpretación es un "arte perdido" es algo muy, muy bueno. Ese arte se practicaba como arte . La optimización debe abordarse por lo que es: un problema de ingeniería que se puede resolver mediante la aplicación cuidadosa de principios de ingeniería sólidos.
Me han pedido decenas de veces a lo largo de los años mi lista de "consejos y trucos" que la gente puede utilizar para optimizar su vbscript / su jscript / sus páginas de servidor activo / su VB / su código C #. Siempre me resisto a esto. Hacer hincapié en "consejos y trucos" es exactamente la forma incorrecta de abordar el rendimiento. De esa forma se obtiene un código que es difícil de entender, difícil de razonar, difícil de mantener, que normalmente no es notablemente más rápido que el código sencillo correspondiente.
La forma correcta de abordar el rendimiento es abordarlo como un problema de ingeniería como cualquier otro problema:
Esto es lo mismo que resolvería cualquier otro problema de ingeniería, como agregar una función: establezca objetivos centrados en el cliente para la función, realice un seguimiento del progreso para realizar una implementación sólida, solucione los problemas a medida que los encuentre a través de un análisis de depuración cuidadoso, siga iterando hasta envía o falla. El rendimiento es una característica.
El análisis del desempeño en sistemas modernos complejos requiere disciplina y enfoque en principios sólidos de ingeniería, no en una bolsa llena de trucos que son estrictamente aplicables a situaciones triviales o poco realistas. Nunca he resuelto un problema de rendimiento del mundo real mediante la aplicación de consejos y trucos.
fuente
Consiga un buen perfilador.
No se moleste en intentar optimizar C # (en realidad, cualquier código) sin un buen generador de perfiles. De hecho, es de gran ayuda tener un generador de perfiles de rastreo y de muestreo a mano.
Sin un buen generador de perfiles, es probable que cree optimizaciones falsas y, lo que es más importante, optimice rutinas que no son un problema de rendimiento en primer lugar.
Los primeros tres pasos para la elaboración de perfiles siempre deben ser 1) Medir, 2) medir y luego 3) medir ....
fuente
4) measure
Pautas de optimización:
A medida que los procesadores continúan haciéndose más rápidos, el principal cuello de botella en la mayoría de las aplicaciones no es la CPU, es el ancho de banda: ancho de banda a la memoria fuera del chip, ancho de banda al disco y ancho de banda a la red.
Empiece por el otro extremo: utilice YSlow para ver por qué su sitio web es lento para los usuarios finales, luego retroceda y corrija los accesos a la base de datos para que no sean demasiado anchos (columnas) ni demasiado profundos (filas).
En los casos muy raros en los que vale la pena hacer algo para optimizar el uso de la CPU, tenga cuidado de no afectar negativamente el uso de la memoria: he visto 'optimizaciones' en las que los desarrolladores han intentado usar la memoria para almacenar en caché los resultados para ahorrar ciclos de CPU. El efecto neto fue reducir la memoria disponible para almacenar en caché las páginas y los resultados de la base de datos, lo que hizo que la aplicación se ejecutara mucho más lentamente. (Vea la regla sobre la medición).
También he visto casos en los que un algoritmo "tonto" no optimizado ha vencido a un algoritmo optimizado "inteligente". Nunca subestime lo buenos que se han vuelto los escritores de compiladores y diseñadores de chips para convertir el código de bucle "ineficiente" en código súper eficiente que puede ejecutarse completamente en la memoria del chip con canalización. Su algoritmo 'inteligente' basado en árbol con un bucle interno desenvuelto que cuenta hacia atrás que pensó que era 'eficiente' puede ser superado simplemente porque no pudo permanecer en la memoria del chip durante la ejecución. (Vea la regla sobre la medición).
fuente
Cuando trabaje con ORM, tenga en cuenta las selecciones N + 1.
List<Order> _orders = _repository.GetOrders(DateTime.Now); foreach(var order in _orders) { Print(order.Customer.Name); }
Si los clientes no se cargan con entusiasmo, esto podría resultar en varios viajes de ida y vuelta a la base de datos.
fuente
fuente
De acuerdo, debo agregar mi favorito: si la tarea es lo suficientemente larga para la interacción humana, use una pausa manual en el depurador.
Vs. un generador de perfiles, esto le brinda una pila de llamadas y valores variables que puede usar para comprender realmente lo que está sucediendo.
Haga esto de 10 a 20 veces y tendrá una buena idea de qué optimización realmente podría marcar la diferencia.
fuente
Si identifica un método como un cuello de botella, pero no sabe qué hacer al respecto , básicamente está atascado.
Así que enumeraré algunas cosas. Todas estas cosas no son soluciones mágicas y aún tendrás que perfilar tu código. Solo estoy haciendo sugerencias sobre cosas que podría hacer y, a veces, puede ayudar. Especialmente los tres primeros son importantes.
fuente
La gente tiene ideas divertidas sobre lo que realmente importa. Stack Overflow está lleno de preguntas sobre, por ejemplo, es
++i
más "eficiente" quei++
. Aquí hay un ejemplo de ajuste de rendimiento real , y es básicamente el mismo procedimiento para cualquier idioma. Si el código simplemente se escribe de cierta manera "porque es más rápido", eso es adivinar.Claro, no escribe código estúpido a propósito, pero si adivinar funciona, no habría necesidad de perfiladores ni técnicas de creación de perfiles.
fuente
La verdad es que no existe el código optimizado perfecto. Sin embargo, puede optimizar para una parte específica del código, en un sistema conocido (o conjunto de sistemas) en un tipo (y recuento) de CPU conocido, una plataforma conocida (¿Microsoft? ¿ Mono ?), Una versión de marco / BCL conocida , una versión CLI conocida, una versión del compilador conocida (errores, cambios de especificación, ajustes), una cantidad conocida de memoria total y disponible, un origen de ensamblaje conocido ( ¿ GAC ? ¿disco? ¿remoto?), con actividad del sistema en segundo plano conocida de otros procesos.
En el mundo real, use un generador de perfiles y observe los bits importantes; Por lo general, las cosas obvias son cualquier cosa que involucre E / S, cualquier cosa que involucre subprocesos (nuevamente, esto cambia enormemente entre versiones) y cualquier cosa que involucre bucles y búsquedas, pero es posible que se sorprenda de lo que el código "obviamente malo" no es realmente un problema, y qué código "obviamente bueno" es un gran culpable.
fuente
Dígale al compilador qué hacer, no cómo hacerlo. Por ejemplo,
foreach (var item in list)
es mejor quefor (int i = 0; i < list.Count; i++)
ym = list.Max(i => i.value);
es mejor quelist.Sort(i => i.value); m = list[list.Count - 1];
.Al decirle al sistema lo que quiere hacer, puede descubrir la mejor manera de hacerlo. LINQ es bueno porque sus resultados no se calculan hasta que los necesita. Si solo usa el primer resultado, no tiene que calcular el resto.
En última instancia (y esto se aplica a toda la programación) minimiza los bucles y minimiza lo que haces en los bucles. Aún más importante es minimizar la cantidad de bucles dentro de sus bucles. ¿Cuál es la diferencia entre un algoritmo O (n) y un algoritmo O (n ^ 2)? El algoritmo O (n ^ 2) tiene un bucle dentro de un bucle.
fuente
Realmente no trato de optimizar mi código, pero a veces lo reviso y uso algo como un reflector para devolver mis programas a la fuente. Es interesante comparar luego lo que me equivoqué con lo que producirá el reflector. A veces encuentro que lo que hice en una forma más complicada se simplificó. Puede que no optimice las cosas, pero me ayuda a ver soluciones más simples a los problemas.
fuente