Como dijo Knuth,
Deberíamos olvidarnos de las pequeñas eficiencias, digamos alrededor del 97% del tiempo: la optimización prematura es la raíz de todos los males.
Esto es algo que a menudo surge en las respuestas de Stack Overflow a preguntas como "¿cuál es el mecanismo de bucle más eficiente", "técnicas de optimización de SQL?" ( y así sucesivamente ). La respuesta estándar a estas preguntas de consejos de optimización es perfilar su código y ver si es un problema primero, y si no lo es, entonces su nueva técnica no es necesaria.
Mi pregunta es, si una técnica en particular es diferente pero no particularmente oscura u ofuscada, ¿se puede realmente considerar una optimización prematura?
Aquí hay un artículo relacionado de Randall Hyde llamado La falacia de la optimización prematura .
Respuestas:
Don Knuth inició el movimiento de la programación alfabetizada porque creía que la función más importante del código de computadora es comunicar la intención del programador a un lector humano . Cualquier práctica de codificación que haga que su código sea más difícil de entender en nombre del rendimiento es una optimización prematura.
Ciertos modismos que se introdujeron en nombre de la optimización se han vuelto tan populares que todos los entienden y se han vuelto esperados , no prematuros. Ejemplos incluyen
Usar aritmética de punteros en lugar de notación de matriz en C, incluido el uso de expresiones idiomáticas como
Volver a vincular variables globales a variables locales en Lua, como en
Más allá de esos modismos, tome atajos bajo su propio riesgo .
Toda optimización es prematura a menos que
Un programa es demasiado lento (muchas personas olvidan esta parte).
Tiene una medición (perfil o similar) que muestra que la optimización podría mejorar las cosas .
(También está permitido optimizar la memoria).
Respuesta directa a la pregunta:
EDITAR : En respuesta a los comentarios, el uso de ordenación rápida en lugar de un algoritmo más simple como la ordenación por inserción es otro ejemplo de un modismo que todos entienden y esperan . (Aunque si escribe su propia rutina de clasificación en lugar de utilizar la rutina de clasificación de la biblioteca, es de esperar que tenga una muy buena razón).
fuente
En mi humilde opinión, el 90% de su optimización debería ocurrir en la etapa de diseño, en función de los requisitos actuales percibidos y, lo que es más importante, los requisitos futuros. Si tiene que sacar un generador de perfiles porque su aplicación no se adapta a la carga requerida, lo dejó demasiado tarde y, en mi opinión, perderá mucho tiempo y esfuerzo sin corregir el problema.
Normalmente, las únicas optimizaciones que valen la pena son aquellas que le otorgan una mejora de rendimiento de un orden de magnitud en términos de velocidad, o un multiplicador en términos de almacenamiento o ancho de banda. Estos tipos de optimizaciones generalmente se relacionan con la selección de algoritmos y la estrategia de almacenamiento, y son extremadamente difíciles de invertir en el código existente. Pueden llegar a influir en la decisión sobre el idioma en el que implementas tu sistema.
Así que mi consejo es que optimice con anticipación, según sus requisitos, no su código, y observe la posible vida útil prolongada de su aplicación.
fuente
while (s[0]==' ') s = s.substring(1)
for(i=0; i<s.len && s[i]==' '; ++i); s=s.substring(i)
--- pero esto requiere conocer los problemas potenciales de rendimiento (los perfiladores son herramientas valiosas para el aprendizaje constante aquí).Si no ha realizado un perfil, es prematuro.
fuente
Um ... Entonces tienes dos técnicas a mano, idénticas en costo (mismo esfuerzo para usar, leer, modificar) y una es más eficiente. No, utilizar el más eficiente no sería, en ese caso, prematuro.
Interrumpir la escritura de su código para buscar alternativas a las construcciones de programación / rutinas de biblioteca comunes en caso de que haya una versión más eficiente en algún lugar, aunque, por lo que sabe, la velocidad relativa de lo que está escribiendo nunca importará. .. Eso es prematuro.
fuente
Este es el problema que veo con todo el concepto de evitar la optimización prematura.
Hay una desconexión entre decirlo y hacerlo.
He realizado muchos ajustes de rendimiento, exprimiendo grandes factores del código que de otro modo estaría bien diseñado, aparentemente sin una optimización prematura. He aquí un ejemplo.
En casi todos los casos, la razón del rendimiento subóptimo es lo que yo llamo generalidad galopante , que es el uso de clases abstractas de múltiples capas y un diseño orientado a objetos minucioso, donde los conceptos simples serían menos elegantes pero completamente suficientes.
Y en el material didáctico donde se enseñan estos conceptos abstractos de diseño, como la arquitectura basada en notificaciones y el ocultamiento de información, donde simplemente establecer una propiedad booleana de un objeto puede tener un efecto dominó ilimitado de las actividades, ¿cuál es la razón dada? Eficiencia .
Entonces, ¿fue una optimización prematura o no?
fuente
Primero, haz que el código funcione. En segundo lugar, verifique que el código sea correcto. Tercero, hazlo rápido.
Cualquier cambio de código que se realice antes de la etapa 3 es definitivamente prematuro. No estoy del todo seguro de cómo clasificar las elecciones de diseño hechas antes de eso (como usar estructuras de datos adecuadas), pero prefiero desviarme hacia el uso de abstracciones con las que es fácil programar en lugar de aquellas que tienen un buen rendimiento, hasta que estoy en una etapa en la que puedo comenzar a usar la creación de perfiles y tener una implementación de referencia correcta (aunque con frecuencia lenta) para comparar los resultados.
fuente
Desde la perspectiva de una base de datos, no considerar el diseño óptimo en la etapa de diseño es, en el mejor de los casos, una temeridad. Las bases de datos no se refactorizan fácilmente. Una vez que están mal diseñados (esto es lo que es un diseño que no considera la optimización, sin importar cómo intente esconderse detrás de la tontería de la optimización prematura), casi nunca se recupera porque la base de datos es demasiado básica para el funcionamiento de todo el sistema. Es mucho menos costoso diseñar correctamente considerando el código óptimo para la situación que esperas que esperar hasta que haya un millón de usuarios y la gente grite porque usaste cursores en toda la aplicación. Otras optimizaciones, como el uso de código sargeable, la selección de los mejores índices posibles, etc., solo tienen sentido en el momento del diseño. Hay una razón por la que se llama así rápido y sucio. Debido a que nunca puede funcionar bien, no use la rapidez como sustituto de un buen código. También, francamente, cuando comprende el ajuste del rendimiento en las bases de datos, puede escribir código que tenga más probabilidades de funcionar bien en el mismo tiempo o menos de lo que se necesita para escribir código que no funciona bien. No tomarse el tiempo para aprender qué es un buen diseño de bases de datos es una pereza del desarrollador, no una mejor práctica.
fuente
De lo que parece estar hablando es de optimización como el uso de un contenedor de búsqueda basado en hash frente a uno indexado como una matriz cuando se realizarán muchas búsquedas clave. Esta no es una optimización prematura, sino algo que debe decidir en la fase de diseño.
El tipo de optimización de la que se trata la regla de Knuth es minimizar la longitud de las rutas de código más comunes, optimizar el código que se ejecuta más, por ejemplo, reescribiendo en ensamblador o simplificando el código, haciéndolo menos general. Pero hacer esto no sirve de nada hasta que esté seguro de qué partes del código necesitan este tipo de optimización y la optimización hará (¿podría?) Hacer que el código sea más difícil de entender o mantener, por lo tanto, "la optimización prematura es la raíz de todos los males".
Knuth también dice que siempre es mejor, en lugar de optimizar, cambiar los algoritmos que usa su programa, el enfoque que toma para un problema. Por ejemplo, mientras que un pequeño ajuste puede darle un aumento del 10% en la velocidad con la optimización, cambiar fundamentalmente la forma en que funciona su programa podría hacerlo 10 veces más rápido.
En reacción a muchos de los otros comentarios publicados sobre esta pregunta: ¡selección de algoritmo! = Optimización
fuente
El punto de la máxima es que, por lo general , la optimización es intrincada y compleja. Y , por lo general , usted, el arquitecto / diseñador / programador / mantenedor, necesita un código claro y conciso para comprender lo que está sucediendo.
Si una optimización en particular es clara y concisa, no dude en experimentar con ella (pero retroceda y compruebe si esa optimización es eficaz). El punto es mantener el código claro y conciso durante todo el proceso de desarrollo, hasta que los beneficios del rendimiento superen los costos inducidos de escribir y mantener las optimizaciones.
fuente
Intento optimizar solo cuando se confirma un problema de rendimiento.
Mi definición de optimización prematura es 'esfuerzo desperdiciado en código que no se sabe que sea un problema de rendimiento'. Definitivamente hay un momento y un lugar para la optimización. Sin embargo, el truco consiste en gastar el costo adicional solo cuando sea importante para el rendimiento de la aplicación y cuando el costo adicional supere el impacto en el rendimiento.
Al escribir código (o una consulta de base de datos) me esfuerzo por escribir código 'eficiente' (es decir, código que realiza su función prevista, rápida y completamente con la lógica más simple razonable). Tenga en cuenta que el código 'eficiente' no es necesariamente lo mismo que 'optimizado' código. Las optimizaciones a menudo introducen una complejidad adicional en el código, lo que aumenta tanto el costo de desarrollo como el de mantenimiento de ese código.
Mi consejo: intente pagar el costo de la optimización solo cuando pueda cuantificar el beneficio.
fuente
Al programar, una serie de parámetros son vitales. Entre estos se encuentran:
La optimización (buscar rendimiento) a menudo se produce a expensas de otros parámetros y debe equilibrarse con la "pérdida" en estas áreas.
Cuando tiene la opción de elegir algoritmos conocidos que funcionan bien, el costo de "optimizar" por adelantado suele ser aceptable.
fuente
La optimización puede ocurrir en diferentes niveles de granularidad, desde un nivel muy alto hasta un nivel muy bajo:
Comience con una buena arquitectura, acoplamiento flexible, modularidad, etc.
Elija las estructuras de datos y los algoritmos adecuados para el problema.
Optimice la memoria, tratando de ajustar más código / datos en la caché. El subsistema de memoria es de 10 a 100 veces más lento que la CPU, y si sus datos se paginan en el disco, es de 1000 a 10,000 veces más lento. Ser cauteloso con el consumo de memoria es más probable que proporcione mayores beneficios que optimizar las instrucciones individuales.
Dentro de cada función, haga un uso adecuado de las declaraciones de control de flujo. (Mueva las expresiones inmutables fuera del cuerpo del bucle. Ponga el valor más común primero en un interruptor / caso, etc.)
Dentro de cada declaración, use las expresiones más eficientes que produzcan el resultado correcto. (Multiplicar contra cambio, etc.)
La meticulosidad sobre si se debe utilizar una expresión de división o una expresión de cambio no es necesariamente una optimización prematura. Solo es prematuro si lo hace sin optimizar primero la arquitectura, las estructuras de datos, los algoritmos, la huella de memoria y el control de flujo.
Y, por supuesto, cualquier optimización es prematura si no define un umbral de rendimiento objetivo.
En la mayoría de los casos, ya sea:
A) Puede alcanzar el umbral de rendimiento de la meta realizando optimizaciones de alto nivel, por lo que no es necesario jugar con las expresiones.
o
B) Incluso después de realizar todas las optimizaciones posibles, no alcanzará el umbral de rendimiento de su objetivo y las optimizaciones de bajo nivel no suponen una diferencia suficiente en el rendimiento para justificar la pérdida de legibilidad.
En mi experiencia, la mayoría de los problemas de optimización se pueden resolver a nivel de arquitectura / diseño o estructura de datos / algoritmo. La optimización para la huella de memoria es a menudo (aunque no siempre) necesaria. Pero rara vez es necesario optimizar el control de flujo y la lógica de expresión. Y en aquellos casos en los que realmente es necesario, rara vez es suficiente.
fuente
La respuesta de Norman es excelente. De alguna manera, rutinariamente realiza alguna "optimización prematura" que son, en realidad, las mejores prácticas, porque se sabe que hacer lo contrario es totalmente ineficiente.
Por ejemplo, para agregar a la lista de Norman:
for (i = 0; i < strlen(str); i++)
(porque strlen aquí es una llamada de función que recorre la cadena cada vez, llamada en cada bucle);for (i = 0 l = str.length; i < l; i++)
y todavía es legible, así que está bien.Y así. Pero tales microoptimizaciones nunca deberían tener el costo de la legibilidad del código.
fuente
La necesidad de utilizar un perfilador debe dejarse para casos extremos. Los ingenieros del proyecto deben saber dónde se encuentran los cuellos de botella en el rendimiento.
Creo que la "optimización prematura" es increíblemente subjetiva.
Si estoy escribiendo algún código y sé que debería usar una tabla hash, lo haré. No lo implementaré de alguna manera defectuosa y luego esperaré a que el informe de error llegue un mes o un año después cuando alguien tenga un problema con él.
El rediseño es más costoso que optimizar un diseño de formas obvias desde el principio.
Obviamente, algunas cosas pequeñas se perderán la primera vez, pero rara vez son decisiones clave de diseño.
Por lo tanto: NO optimizar un diseño es, en mi opinión, un olor a código en sí mismo.
fuente
Vale la pena señalar que la cita original de Knuth provino de un artículo que escribió promoviendo el uso
goto
en áreas cuidadosamente seleccionadas y medidas como una forma de eliminar puntos críticos. Su cita fue una salvedad que agregó para justificar su razón de ser para usargoto
con el fin de acelerar esos ciclos críticos.Y continúa:
Tenga en cuenta cómo usó "optimizado" entre comillas (el software probablemente no sea realmente eficiente). También tenga en cuenta que no solo está criticando a estos programadores "tontos y tontos", sino también a las personas que reaccionan sugiriendo que siempre se deben ignorar las pequeñas ineficiencias. Finalmente, a la parte que se cita con frecuencia:
... y luego algo más sobre la importancia de las herramientas de creación de perfiles:
La gente ha hecho un mal uso de su cita en todas partes, a menudo sugiriendo que las microoptimizaciones son prematuras cuando todo su artículo abogaba por microoptimizaciones. Uno de los grupos de personas que estaba criticando que se hacen eco de esta "sabiduría convencional", como él dijo de ignorar siempre las eficiencias en lo pequeño, a menudo hace un mal uso de su cita, que originalmente estaba dirigida, en parte, contra esos tipos que desalientan todas las formas de microoptimización. .
Sin embargo, era una cita a favor de las microoptimizaciones aplicadas de manera apropiada cuando las usa una mano experimentada que sostiene un perfilador. El equivalente analógico actual podría ser como: "La gente no debería intentar optimizar su software, pero los asignadores de memoria personalizados pueden marcar una gran diferencia cuando se aplican en áreas clave para mejorar la localidad de referencia" , o " Código SIMD escrito a mano con un Por lo tanto, una repetición es realmente difícil de mantener y no debería usarla por todas partes, pero puede consumir memoria mucho más rápido cuando se aplica adecuadamente por una mano experimentada y guiada " .
Cada vez que intente promover microoptimizaciones cuidadosamente aplicadas como Knuth promovió anteriormente, es bueno incluir una exención de responsabilidad para disuadir a los novatos de emocionarse demasiado y hacer intentos ciegos de optimización, como reescribir todo su software para usar
goto
. Eso es en parte lo que estaba haciendo. Su cita fue efectivamente parte de un gran descargo de responsabilidad, al igual que alguien que salta en motocicleta sobre un pozo de fuego en llamas podría agregar un descargo de responsabilidad de que los aficionados no deberían intentar esto en casa mientras simultáneamente critican a aquellos que lo intentan sin el conocimiento y el equipo adecuados y se lastiman. .Lo que él consideró "optimizaciones prematuras" fueron optimizaciones aplicadas por personas que efectivamente no sabían lo que estaban haciendo: no sabían si la optimización era realmente necesaria, no medían con las herramientas adecuadas, tal vez no entendían la naturaleza de su compilador o arquitectura de computadora, y sobre todo, eran "pennywise-and-pound-tont", lo que significa que pasaron por alto las grandes oportunidades para optimizar (ahorrar millones de dólares) al tratar de reducir centavos, y todo mientras crean código que no pueden depurar y mantener más eficazmente.
Si no encaja en la categoría "pennywise-and-pound-tonish", entonces no está optimizando prematuramente según los estándares de Knuth, incluso si está utilizando un
goto
para acelerar un ciclo crítico (algo que es poco probable para ayudar mucho contra los optimizadores actuales, pero si lo hiciera, y en un área realmente crítica, entonces no estaría optimizando prematuramente). Si realmente está aplicando lo que está haciendo en áreas que son realmente necesarias y ellos realmente se benefician de ello, entonces lo está haciendo muy bien a los ojos de Knuth.fuente
Para mí, la optimización prematura significa tratar de mejorar la eficiencia de su código antes de tener un sistema en funcionamiento, y antes de haberlo perfilado y saber dónde está el cuello de botella. Incluso después de eso, la legibilidad y la capacidad de mantenimiento deberían estar antes que la optimización en muchos casos.
fuente
No creo que las mejores prácticas reconocidas sean optimizaciones prematuras. Se trata más de grabar tiempo en los posibles problemas de rendimiento según los escenarios de uso. Un buen ejemplo: si quema una semana tratando de optimizar el reflejo sobre un objeto antes de tener pruebas de que es un cuello de botella, está optimizando prematuramente.
fuente
A menos que descubra que necesita más rendimiento de su aplicación, ya sea debido a una necesidad del usuario o de la empresa, hay pocas razones para preocuparse por la optimización. Incluso entonces, no hagas nada hasta que hayas perfilado tu código. Luego ataca las partes que requieren más tiempo.
fuente
A mi modo de ver, si optimizas algo sin saber cuánto rendimiento puedes obtener en diferentes escenarios, ES una optimización prematura. El objetivo del código debería hacer que sea más fácil de leer para los humanos.
fuente
Como publiqué en una pregunta similar, las reglas de optimización son:
1) No optimices
2) (solo para expertos) Optimizar más tarde
¿Cuándo es prematura la optimización? Generalmente.
La excepción tal vez esté en su diseño o en un código bien encapsulado que se usa mucho. En el pasado, trabajé en algún código de tiempo crítico (una implementación RSA) donde mirar el ensamblador que el compilador produjo y eliminar una sola instrucción innecesaria en un ciclo interno dio un 30% de aceleración. Pero, la aceleración del uso de algoritmos más sofisticados fue órdenes de magnitud más que eso.
Otra pregunta que debe hacerse al optimizar es "¿estoy haciendo aquí el equivalente a optimizar para un módem de 300 baudios?" . En otras palabras, ¿la ley de Moore hará que su optimización sea irrelevante en poco tiempo? Muchos problemas de escalado pueden resolverse simplemente lanzando más hardware al problema.
Por último, pero no menos importante, es prematuro optimizar antes de que el programa vaya demasiado lento. Si está hablando de una aplicación web, puede ejecutarla bajo carga para ver dónde están los cuellos de botella, pero lo más probable es que tenga los mismos problemas de escala que la mayoría de los otros sitios y se aplicarán las mismas soluciones.
editar: Por cierto, con respecto al artículo vinculado, cuestionaría muchas de las suposiciones hechas. En primer lugar, no es cierto que la ley de Moore dejó de funcionar en los años 90. En segundo lugar, no es obvio que el tiempo del usuario sea más valioso que el tiempo del programador. La mayoría de los usuarios (por decir lo menos) no están usando frenéticamente cada ciclo de CPU disponible de todos modos, probablemente están esperando que la red haga algo. Además, existe un costo de oportunidad cuando el tiempo del programador se desvía de la implementación de otra cosa, a reducir unos milisegundos algo que el programa hace mientras el usuario está hablando por teléfono. Cualquier cosa más larga que eso no suele ser optimización, es corrección de errores.
fuente