¿Es la optimización prematura realmente la raíz de todo mal?

197

Un colega mío hoy cometió una clase llamada ThreadLocalFormat , que básicamente movió instancias de clases de formato Java a un subproceso local, ya que no son seguras para subprocesos y son "relativamente caras" de crear. Escribí una prueba rápida y calculé que podía crear 200,000 instancias por segundo, le pregunté si estaba creando esa cantidad, a lo que respondió "en ninguna parte cerca de esa cantidad". Es un gran programador y todos los miembros del equipo tienen una gran habilidad, por lo que no tenemos problemas para entender el código resultante, pero claramente fue un caso de optimización donde no hay una necesidad real. Él respaldó el código a mi petición. ¿Qué piensas? ¿Es este un caso de "optimización prematura" y qué tan grave es realmente?

    
pregunta Craig Day 29.12.2015 - 08:56

17 respuestas

303

Es importante tener en cuenta la cita completa:

  

Debemos olvidarnos de las pequeñas eficiencias, digamos que aproximadamente el 97% de las veces: la optimización prematura es la raíz de todo mal. Sin embargo, no debemos dejar pasar nuestras oportunidades en ese 3% crítico.

Lo que esto significa es que, en ausencia de problemas de rendimiento medidos, no debería optimizar porque piensa obtendrá una ganancia de rendimiento. Hay optimizaciones obvias (como no hacer concatenación de cadenas dentro de un bucle cerrado) pero cualquier cosa que no sea una optimización trivialmente clara debe evitarse hasta que pueda medirse.

Los mayores problemas con la "optimización prematura" son que puede introducir errores inesperados y puede ser una gran pérdida de tiempo.

    
respondido por el Scott Dorman 11.12.2014 - 18:46
102

Las optimizaciones micro prematuras son la raíz de todo mal, porque las optimizaciones micro omiten el contexto. Casi nunca se comportan como se espera.

¿Cuáles son algunas buenas optimizaciones iniciales en el orden de importancia?

  • Optimizaciones arquitectónicas (estructura de la aplicación, la forma en que se compone de componentes y capas)
  • Optimizaciones de flujo de datos (dentro y fuera de la aplicación)

Algunas optimizaciones del ciclo de desarrollo medio:

  • Las estructuras de datos introducen nuevas estructuras de datos que tienen un mejor rendimiento o una menor sobrecarga si es necesario
  • Algoritmos (ahora es un buen momento para comenzar a decidir entre quicksort3 y heapsort ;-))

Algunas optimizaciones del ciclo de desarrollo final

  • Buscar hotpots de código (bucles ajustados, que deben optimizarse)
  • Optimizaciones basadas en perfiles de partes computacionales del código
  • Las optimizaciones micro pueden hacerse ahora como se hacen en el contexto de la aplicación y su impacto se puede medir correctamente.

No todas las primeras optimizaciones son malas, las micro optimizaciones son malas si se realizan en el momento equivocado en el ciclo de vida del desarrollo , ya que pueden afectar negativamente a la arquitectura, pueden afectar negativamente a la productividad inicial, pueden ser irrelevantes. sabio o incluso tiene un efecto perjudicial al final del desarrollo debido a las diferentes condiciones ambientales.

Si el rendimiento es motivo de preocupación (y siempre debería serlo), siempre piense en grande . El rendimiento es una imagen más amplia y no se trata de cosas como: ¿debo usar int o long ?. Vaya a Arriba Abajo cuando trabaje con rendimiento en lugar de Abajo Arriba .

    
respondido por el Pop Catalin 06.10.2015 - 15:07
49

la optimización sin la primera medición es casi siempre prematura.

Creo que eso es cierto en este caso, y también en el caso general.

    
respondido por el Jeff Atwood 17.10.2008 - 11:29
39

La optimización es "malvada" si causa:

  • código menos claro
  • significativamente más código
  • código menos seguro
  • tiempo de programador perdido

En su caso, parece que ya se gastó un poco de tiempo en programadores, el código no era demasiado complejo (una suposición de su comentario que todos los integrantes del equipo podrían entender), y el código es un poco más futuro prueba (ahora estoy seguro, si entendiera su descripción). Suena como un poquito malvado. :)

    
respondido por el John Mulder 17.10.2008 - 10:42
32

Me sorprende que esta pregunta tenga 5 años, y sin embargo, nadie ha publicado más de lo que Knuth tenía que decir que un par de oraciones. El par de párrafos que rodean la famosa cita lo explican bastante bien. El documento que se cita se llama " Programación estructurada con ir a Estados Unidos ", y mientras esté cerca Con 40 años de edad, se trata de una controversia y un movimiento de software que ambos ya no existen, y tiene ejemplos en lenguajes de programación que muchas personas nunca han escuchado, una cantidad sorprendentemente grande de lo que dice todavía se aplica.

Aquí hay una cita más grande (de la página 8 del pdf, página 268 en el original):

  

La mejora en la velocidad del Ejemplo 2 al Ejemplo 2a es solo de alrededor del 12%, y muchas personas dirían que eso es insignificante. La sabiduría convencional compartida por muchos de los ingenieros de software de hoy en día exige ignorar la eficiencia en lo pequeño; pero creo que esto es simplemente una reacción exagerada a los abusos que ven siendo practicados por programadores tontos, que no pueden depurar o mantener sus programas "optimizados". En las disciplinas de ingeniería establecidas, una mejora del 12%, fácil de obtener, nunca se considera marginal; y creo que el mismo punto de vista debería prevalecer en la ingeniería de software. Por supuesto, no me molestaría en realizar tales optimizaciones en un trabajo de una sola vez, pero cuando se trata de preparar programas de calidad, no quiero limitarme a herramientas que me nieguen tales eficiencias.

     

No hay duda de que el grial de la eficiencia lleva al abuso. Los programadores pierden enormes cantidades de tiempo pensando o preocupándose por la velocidad de las partes no críticas de sus programas, y estos intentos de eficiencia en realidad tienen un fuerte impacto negativo cuando se consideran la depuración y el mantenimiento. deberíamos olvidar las pequeñas eficiencias, digamos que aproximadamente el 97% de las veces: la optimización prematura es la raíz de todo mal.

     

Sin embargo, no debemos dejar pasar nuestras oportunidades en ese 3% crítico. Un buen programador no se dejará llevar por la complacencia con tal razonamiento, será sabio que mire cuidadosamente el código crítico; pero solo después de que ese código haya sido identificado. A menudo es un error hacer juicios a priori sobre qué partes de un programa son realmente críticas, ya que la experiencia universal de los programadores que han estado utilizando herramientas de medición ha sido que sus intuiciones intuitivas fallan.

Otra buena parte de la página anterior:

  

Mi propio estilo de programación, por supuesto, ha cambiado durante la última década, de acuerdo con las tendencias de los tiempos (p. ej., ya no soy tan complicado y uso menos recursos), pero el cambio más importante en mi estilo Se ha debido a este fenómeno de bucle interno. Ahora miro con un ojo extremadamente ictérico a cada operación en un bucle interno crítico, buscando modificar mi programa y la estructura de datos (como en el cambio del Ejemplo 1 al Ejemplo 2) para que algunas de las operaciones puedan ser eliminadas. Las razones de este enfoque son que: a) no lleva mucho tiempo, ya que el bucle interno es corto; b) la recompensa es real; y c) luego puedo permitirme ser menos eficiente en las otras partes de mis programas, que por lo tanto son más legibles y más fáciles de escribir y depurar.

    
respondido por el Michael Shaw 27.10.2013 - 04:20
16

A menudo he visto que esta cita se utiliza para justificar un código o código obviamente malo que, si bien su rendimiento no se ha medido, probablemente podría acelerarse más fácilmente, sin aumentar el tamaño del código ni comprometer su legibilidad.

En general, creo que las microoptimizaciones iniciales pueden ser una mala idea. Sin embargo, las macro-optimizaciones (cosas como elegir un algoritmo O (registro N) en lugar de O (N ^ 2)) a menudo valen la pena y se deben hacer antes, ya que puede ser un desperdicio escribir un algoritmo O (N ^ 2) y luego deséchelo completamente a favor de un enfoque O (registro N).

Tenga en cuenta que las palabras pueden ser : si el algoritmo O (N ^ 2) es simple y fácil de escribir, puede desecharlo más tarde sin mucha culpa si resulta ser demasiado lento. Pero si ambos algoritmos son igualmente complejos, o si la carga de trabajo esperada es tan grande que ya sabe que necesitará la más rápida, entonces la optimización temprana es una decisión de ingeniería sólida que reducirá su carga de trabajo total a largo plazo.

Por lo tanto, en general, creo que el enfoque correcto es averiguar cuáles son sus opciones antes de comenzar a escribir el código y elegir conscientemente el mejor algoritmo para su situación. Más importante aún, la frase "la optimización prematura es la raíz de todo mal" no es excusa para la ignorancia. Los desarrolladores de carreras deberían tener una idea general de cuánto cuestan las operaciones comunes; deberían saber, por ejemplo,

  • que las cadenas cuestan más que números
  • que los lenguajes dinámicos son mucho más lentos que los lenguajes de tipo estático
  • las ventajas de las listas de matrices / vectores sobre las listas vinculadas, y viceversa
  • cuándo usar una tabla hash, cuándo usar un mapa ordenado y cuándo usar un montón
  • que (si funcionan con dispositivos móviles) "double" e "int" tienen un rendimiento similar en los equipos de escritorio (FP puede ser incluso más rápido) pero "double" puede ser cien veces más lento en dispositivos móviles de gama baja sin FPU;
  • que la transferencia de datos a través de Internet es más lenta que la del disco duro, las unidades de disco duro son mucho más lentas que la RAM, la RAM es mucho más lenta que la caché y los registros L1, y las operaciones de Internet pueden bloquearse indefinidamente (y fallar en cualquier momento).

Y los desarrolladores deben estar familiarizados con una caja de herramientas de estructuras de datos y algoritmos para que puedan usar fácilmente las herramientas adecuadas para el trabajo.

Tener un montón de conocimientos y una caja de herramientas personal le permite optimizar casi sin esfuerzo. Poner mucho esfuerzo en una optimización que podría ser innecesaria es malvada (y admito que caigo en esa trampa más de una vez). Pero cuando la optimización es tan fácil como elegir un conjunto / tabla hash en lugar de una matriz, o almacenar una lista de números en doble [] en lugar de en la cadena [], ¿por qué no? Puede que no esté de acuerdo con Knuth aquí, no estoy seguro, pero creo que él estaba hablando de optimización de bajo nivel mientras que estoy hablando de optimización de alto nivel.

Recuerde, esa cita es originalmente de 1974. En 1974 las computadoras eran lentas y la potencia de cómputo era costosa, lo que dio a algunos desarrolladores una tendencia a sobre-optimizar, línea por línea. Creo que eso es contra lo que Knuth estaba presionando. No estaba diciendo "no te preocupes por el rendimiento", porque en 1974 eso sería una locura. Knuth explicaba cómo optimizar; en resumen, uno debe centrarse solo en los cuellos de botella y, antes de hacerlo, debe realizar mediciones para encontrar los cuellos de botella.

Tenga en cuenta que no puede encontrar los cuellos de botella hasta que haya escrito un programa para medir, lo que significa que deben tomarse algunas decisiones de rendimiento antes de que exista algo para medir. A veces, estas decisiones son difíciles de cambiar si se equivocan. Por esta razón, es bueno tener una idea general de lo que cuestan las cosas para que pueda tomar decisiones razonables cuando no haya datos disponibles.

La rapidez con la que se optimiza y cuánto debe preocuparse por el rendimiento depende del trabajo. Al escribir scripts que solo ejecutará unas cuantas veces, preocuparse por el rendimiento en general es una completa pérdida de tiempo. Pero si trabaja para Microsoft u Oracle y está trabajando en una biblioteca que miles de otros desarrolladores van a usar de miles de formas diferentes, puede ser útil para optimizar el infierno, así que que puede cubrir todos los diversos casos de uso eficientemente. Aun así, la necesidad de rendimiento siempre debe equilibrarse con la necesidad de legibilidad, facilidad de mantenimiento, elegancia, extensibilidad, etc.

    
respondido por el Qwertie 01.05.2012 - 23:58
13

Personalmente, tal como se describe en un tema anterior , no No crea que la optimización temprana es mala en situaciones en las que sabe que tendrá problemas de rendimiento. Por ejemplo, escribo software de análisis y modelado de superficies, donde trato con decenas de millones de entidades. La planificación para un rendimiento óptimo en la etapa de diseño es muy superior a la optimización tardía de un diseño débil.

Otra cosa a considerar es cómo se escalará su aplicación en el futuro. Si considera que su código tendrá una larga vida útil, también es una buena idea optimizar el rendimiento en la etapa de diseño.

En mi experiencia, la optimización tardía ofrece pocas recompensas a un precio alto. La optimización en la etapa de diseño, a través de la selección de algoritmos y ajustes, es mucho mejor. Dependiendo de un generador de perfiles para entender cómo funciona su código no es una buena manera de obtener un código de alto rendimiento, debe saber esto de antemano.

    
respondido por el Shane MacLaughlin 23.05.2017 - 14:40
9

De hecho, aprendí que la no optimización prematura es más a menudo la raíz de todo mal.

Cuando la gente escribe software, inicialmente tendrá problemas, como inestabilidad, funciones limitadas, mala capacidad de uso y mal rendimiento. Todo esto generalmente se arregla cuando el software madura.

Todos estos, excepto el rendimiento. A nadie parece importarle el rendimiento. El motivo es simple: si un software falla, alguien solucionará el error y, si falta una función, alguien lo implementará y ejecutará, si el software tiene un rendimiento deficiente, en muchos casos no se debe a una microoptimización faltante, pero Debido al mal diseño y nadie va a tocar el diseño del software. NUNCA.

Mira a Bochs. Es lento como el infierno. ¿Alguna vez será más rápido? Tal vez, pero sólo en el rango de unos pocos por ciento. Nunca obtendrá un rendimiento comparable al del software de virtualización como VMWare o VBox o incluso QEMU. ¡Porque es lento por diseño!

Si el problema de un software es que es lento, entonces es MUY lento y esto solo puede solucionarse mejorando el rendimiento por una multitud. + 10% simplemente no hará un software lento rápido. Y, por lo general, no obtendrá más del 10% de las optimizaciones posteriores.

Entonces, si el rendimiento es ALGUNO importante para su software, debe tenerlo en cuenta desde el principio, cuando lo diseñe, en lugar de pensar "oh, sí, es lento, pero podemos mejorarlo más adelante". ¡Porque no puedes!

Sé que realmente no se ajusta a su caso específico, pero responde a la pregunta general "¿Es la optimización prematura realmente la raíz de todo mal?" - Con un claro NO.

Cada optimización, como cualquier característica, etc. debe diseñarse con cuidado e implementarse con cuidado. Y eso incluye una evaluación adecuada de costo y beneficio. No optimice un algoritmo para guardar algunos ciclos aquí y allá, cuando no cree una ganancia de rendimiento mensurable.

A modo de ejemplo: puede mejorar el rendimiento de una función incorporándola, posiblemente ahorrando un puñado de ciclos, pero al mismo tiempo es probable que aumente el tamaño de su ejecutable, lo que aumenta las posibilidades de TLB y la caché falla y le cuesta a miles de ciclos o incluso operaciones de paginación, que eliminarán el rendimiento por completo. Si no entiende estas cosas, su "optimización" puede resultar mala.

La optimización estúpida es más perversa que la optimización "prematura", pero ambas son aún mejores que la no optimización prematura.

    
respondido por el gnat 11.12.2014 - 19:08
6

Hay dos problemas con la orden de compra: en primer lugar, el tiempo de desarrollo se usa para trabajos no esenciales, que se pueden usar para escribir más funciones o corregir más errores, y en segundo lugar, la falsa sensación de seguridad de que el código se está ejecutando de manera eficiente. PO a menudo implica la optimización del código que no va a ser el cuello de la botella, sin notar el código que lo hará. El bit "prematuro" significa que la optimización se realiza antes de que se identifique un problema con las mediciones adecuadas.

Básicamente, sí, esto suena como una optimización prematura, pero no necesariamente lo retrocederé a menos que presente errores. Después de todo, se ha optimizado ahora (!)

    
respondido por el harriyott 17.10.2008 - 10:39
3

Creo que es lo que Mike Cohn denomina "chapado en oro" al código, es decir, dedicar tiempo a cosas que podrían ser agradables pero que no son necesarias.

Aconsejó no hacerlo.

P.S. El "chapado en oro" podría ser una especie de funcionalidad de campana y silbato. Cuando observa el código, toma la forma de optimización innecesaria, clases "preparadas para el futuro", etc.

    
respondido por el Ilya Kochetov 17.10.2008 - 10:42
3

Como no hay problemas para entender el código, este caso podría considerarse una excepción.

Pero, en general, la optimización conduce a un código menos legible y menos comprensible y se debe aplicar solo cuando sea necesario. Un ejemplo simple: si sabe que debe ordenar solo un par de elementos, use BubbleSort. Pero si sospecha que los elementos podrían aumentar y no sabe cuánto, entonces la optimización con QuickSort (por ejemplo) no es mala, sino una obligación. Y esto debe ser considerado durante el diseño del programa.

    
respondido por el m_pGladiator 17.10.2008 - 10:40
3

Descubrí que el problema con la optimización prematura ocurre principalmente cuando se vuelve a escribir el código existente para ser más rápido. Puedo ver cómo podría ser un problema escribir una optimización complicada en primer lugar, pero sobre todo veo una optimización prematura alzando su fea cabeza al arreglar lo que no se sabe que se haya roto.

Y el peor ejemplo de esto es cuando veo que alguien está reimplementando características de una biblioteca estándar. Esa es una gran bandera roja. Como, una vez vi a alguien implementar rutinas personalizadas para la manipulación de cadenas porque le preocupaba que los comandos integrados fueran demasiado lentos.

Esto da como resultado un código que es más difícil de entender (mal) y que quema mucho tiempo en el trabajo que probablemente no sea útil (mal).

    
respondido por el jhocking 29.05.2011 - 14:54
3

Desde una perspectiva diferente, es mi experiencia que la mayoría de los programadores / desarrolladores no planean el éxito y el "prototipo" casi siempre se convierte en la versión 1.0. Tengo experiencia de primera mano con 4 productos originales separados en los que el front-end elegante, sexy y altamente funcional (básicamente la interfaz de usuario) dio como resultado la adopción y el entusiasmo de los usuarios. En cada uno de estos productos, los problemas de rendimiento comenzaron a aparecer en tiempos relativamente cortos (1 a 2 años), especialmente cuando los clientes más grandes y más exigentes comenzaron a adoptar el producto. Muy pronto el rendimiento dominó la lista de problemas, aunque el desarrollo de nuevas características dominó la lista de prioridades de la administración. Los clientes se frustraron cada vez más a medida que cada lanzamiento incorporaba nuevas funciones que parecían excelentes, pero que eran casi inaccesibles debido a problemas de rendimiento.

Por lo tanto, fallas de diseño e implementación muy fundamentales que fueron de poca o ninguna preocupación en el "prototipo" se convirtieron en obstáculos importantes para el éxito a largo plazo de los productos (y las compañías).

La demostración de su cliente puede verse y tener un gran rendimiento en su computadora portátil con XML DOM, SQL Express y una gran cantidad de datos en caché del lado del cliente. El sistema de producción probablemente colapsará una grabación si tiene éxito.

En 1976, aún estábamos debatiendo las formas óptimas de calcular una raíz cuadrada o clasificar una matriz grande y el adagio de Don Knuth se dirigió al error de centrarse en optimizar ese tipo de rutina de bajo nivel al principio del proceso de diseño en lugar de centrarse en resolviendo el problema y luego optimizando las regiones localizadas del código.

Cuando uno repite el adagio como una excusa para no escribir código eficiente (C ++, VB, T-SQL o de otro tipo), o para no diseñar correctamente el almacén de datos, o para no considerar la arquitectura de trabajo en red, entonces son IMO. solo demostrando una comprensión muy superficial de la naturaleza real de nuestro trabajo. Rayo

    
respondido por el Ray 11.12.2014 - 18:09
1

Supongo que depende de cómo se define "prematuro". Hacer que la funcionalidad de bajo nivel sea rápida cuando estás escribiendo no es intrínsecamente malo. Creo que es un malentendido de la cita. A veces creo que esa cita podría hacer con un poco más de calificación. Sin embargo, repetiría los comentarios de m_pGladiator sobre la legibilidad.

    
respondido por el Dominic Rodger 17.10.2008 - 10:42
1

La respuesta es: depende. Argumentaré que la eficiencia es un gran problema para ciertos tipos de trabajo, como las consultas de bases de datos complejas. En muchos otros casos, la computadora pasa la mayor parte del tiempo esperando la intervención del usuario, por lo que optimizar la mayoría del código es, en el mejor de los casos, una pérdida de esfuerzo y, en el peor, contraproducente.

En algunos casos, puede diseñar para la eficiencia o el rendimiento (percibido o real), seleccionando un algoritmo apropiado o diseñando una interfaz de usuario para que, por ejemplo, ciertas operaciones costosas se realicen en segundo plano. En muchos casos, la creación de perfiles u otras operaciones para determinar puntos de acceso le brindarán un beneficio de 10/90.

Un ejemplo de esto que puedo describir es el modelo de datos que una vez hice para un sistema judicial de gestión de casos que tenía alrededor de 560 tablas. Comenzó normalizado ("bellamente normalizado" como lo expresó el consultor de cierta firma de los 5 grandes) y solo tuvimos que poner cuatro elementos de datos desnormalizados:

  • Una vista materializada para admitir una pantalla de búsqueda

  • Una tabla mantenida por activador para admitir otra pantalla de búsqueda que no se pudo realizar con una vista materializada.

  • Una tabla de informes desnormalizada (esto solo existía porque tuvimos que asumir algunos informes de rendimiento cuando un proyecto de almacén de datos se conservó)

  • Una tabla mantenida por un activador para una interfaz que tuvo que buscar el más reciente de un gran número de eventos dispares dentro del sistema.

Este fue (en ese momento) el proyecto J2EE más grande en Australasia, más de 100 años de tiempo para desarrolladores, y tenía 4 elementos desnormalizados en el esquema de la base de datos, uno de los cuales realmente no pertenecía allí. p>     

respondido por el ConcernedOfTunbridgeWells 01.05.2012 - 18:41
1

La optimización prematura no es la raíz de TODOS los males, eso es seguro. Sin embargo, existen inconvenientes:

  • inviertes más tiempo durante el desarrollo
  • inviertes más tiempo en probarlo
  • inviertes más tiempo corrigiendo errores que de lo contrario no estarían allí

En lugar de una optimización prematura, uno podría hacer pruebas de visibilidad temprana, para ver si hay una necesidad real de una mejor optimización.

    
respondido por el 2 revs, 2 users 74%Herr_Alien 11.12.2014 - 18:47
1

La mayoría de los que se adhieren a "PMO" (es decir, la cita parcial) dicen que las optimizaciones deben basarse en mediciones y que las mediciones no se pueden realizar hasta el final.

También es mi experiencia en el desarrollo de grandes sistemas que las pruebas de rendimiento se realizan al final, a medida que el desarrollo se acerca a su finalización.

Si tuviéramos que seguir los "consejos" de estas personas, todos los sistemas serían extremadamente lentos. También serían costosos porque sus necesidades de hardware son mucho mayores que las previstas originalmente.

Durante mucho tiempo he recomendado realizar pruebas de rendimiento a intervalos regulares en el proceso de desarrollo: indicará la presencia de un nuevo código (donde antes no había ninguno) y el estado del código existente.

  • El rendimiento del código recién implementado se puede comparar con ese de código existente, similar. Una "sensación" para el rendimiento del nuevo código se establecerá con el tiempo.
  • Si el código existente de repente se vuelve loco, entiendes que algo le ha pasado y puedes investigarlo inmediatamente, no (mucho) más tarde, cuando afecta a todo el sistema.

Otra idea favorita es instrumentar software en el nivel de bloque de funciones. A medida que el sistema se ejecuta, recopila información sobre los tiempos de ejecución de los bloques de funciones. Cuando se realiza una actualización del sistema, se puede determinar qué bloques de función se desempeñan como lo hicieron en la versión anterior y cuáles se han deteriorado. En la pantalla de un software, se puede acceder a los datos de rendimiento desde el menú de ayuda.

Echa un vistazo a esta excelente pieza sobre lo que la PMO puede o no significa.

    
respondido por el Olof Forshell 06.10.2015 - 15:15

Lea otras preguntas en las etiquetas