¿Está bien reemplazar el código optimizado con código legible?

78

A veces te encuentras con una situación en la que tienes que extender / mejorar algún código existente. Verá que el código antiguo es muy sencillo, pero también es difícil de extender y lleva tiempo leerlo.

¿Es una buena idea reemplazarlo con código moderno?

Hace algún tiempo me gustó el enfoque lean, pero ahora, me parece que es mejor sacrificar muchas optimizaciones en favor de abstracciones más altas, mejores interfaces y código más legible y extensible.

Los compiladores también parecen estar mejorando, por lo que cosas como struct abc = {}silenciosamente se convierten en memsets, shared_ptrs están produciendo el mismo código que el puntero sin formato, las plantillas funcionan súper bien porque producen un código súper delgado, y así sucesivamente.

Pero aún así, a veces se ven matrices basadas en pila y funciones antiguas de C con alguna lógica oscura, y generalmente no están en la ruta crítica.

¿Es una buena idea cambiar ese código si tiene que tocar un pequeño fragmento de cualquier manera?

Codificador
fuente
20
La legibilidad y las optimizaciones no se oponen la mayor parte del tiempo.
deadalnix
23
¿Puede la legibilidad mejorar con algunos comentarios?
YetAnotherUser
17
Es preocupante que la OOP-ificación se considere 'código moderno'
James
77
como la filosofía de slackware: si no está roto, no lo arregles, al menos tienes una muy, muy buena razón para hacerlo
osdamv
55
Por código optimizado, ¿se refiere al código optimizado real o al llamado código optimizado?
dan04

Respuestas:

115

¿Dónde?

  • En una página de inicio de un sitio web a escala de Google, no es aceptable. Mantenga las cosas lo más rápido posible.

  • En una parte de una aplicación que utiliza una persona una vez al año, es perfectamente aceptable sacrificar el rendimiento para obtener legibilidad del código.

En general, ¿cuáles son los requisitos no funcionales para la parte del código en la que está trabajando? Si una acción debe realizarse por debajo de 900 ms. en un contexto dado (máquina, carga, etc.) el 80% del tiempo, y en realidad, funciona por debajo de 200 ms. El 100% del tiempo, seguro, hace que el código sea más legible incluso si puede afectar ligeramente el rendimiento. Si, por otro lado, la misma acción nunca se realizó en menos de diez segundos, bueno, debería tratar de ver qué hay de malo en el rendimiento (o el requisito en primer lugar).

Además, ¿cómo la mejora de la legibilidad disminuirá el rendimiento? A menudo, los desarrolladores están adaptando el comportamiento cercano a la optimización prematura: tienen miedo de aumentar la legibilidad, creyendo que destruirá drásticamente el rendimiento, mientras que el código más legible pasará unos microsegundos más haciendo la misma acción.

MainMa
fuente
47
+1! Si no tiene números, obtenga algunos números. Si no tiene tiempo para obtener números, no tiene tiempo para cambiarlo.
Tacroy
49
A menudo, los desarrolladores están "optimizando" basándose en mitos y malentendidos, por ejemplo, suponiendo que "C" es más rápido que "C ++" y evitando las características de C ++ por la sensación general de que las cosas son más rápidas sin números que lo respalden. Me recuerda a un desarrollador de C que seguí que pensó que gotoera más rápido que para los bucles. Irónicamente, el optimizador funcionó mejor con bucles for, por lo que hizo que el código fuera más lento y difícil de leer.
Gort the Robot
66
En lugar de agregar otra respuesta, hice +1 en esta respuesta. Si entender los fragmentos de código es tan importante, coméntelos bien. Trabajé en un entorno C / C ++ / Assembly con código heredado de una década con docenas de colaboradores. Si el código funciona, déjelo solo y vuelva a trabajar.
Chris K
Es por eso que tiendo a escribir solo código legible. Se puede alcanzar el rendimiento cortando los pocos puntos calientes.
Luca
36

Usualmente no .

Cambiar el código puede causar problemas imprevistos en otras partes del sistema (que a veces pueden pasar desapercibidas hasta mucho más tarde en un proyecto si no tiene una unidad sólida y pruebas de humo en el lugar). Generalmente uso la mentalidad de "si no está roto, no lo arregles".

La excepción a esta regla es si está implementando una nueva característica que toca este código. Si, en ese punto, no tiene sentido y la refactorización realmente necesita llevarse a cabo, entonces hágalo siempre y cuando el tiempo de refactorización (y suficiente prueba y amortiguación para lidiar con los problemas) se tengan en cuenta en las estimaciones.

Por supuesto, perfil, perfil, perfil , especialmente si se trata de un área de ruta crítica.

Demian Brecht
fuente
2
Sí, pero asume que la optimización era necesaria. No siempre sabemos si fue así, y probablemente queremos determinar esto primero.
haylem
2
@haylem: No, supongo que el código funciona tal como está. También supongo que la refactorización del código invariablemente causará problemas en otras partes del sistema (a menos que esté lidiando con un fragmento trivial de código que no tiene dependencias externas).
Demian Brecht
Hay algo de verdad en esta respuesta, e irónicamente, esto se debe a que los desarrolladores rara vez documentan, entienden, comunican o incluso prestan atención a los problemas. Si los desarrolladores tienen una comprensión más profunda de los problemas que han sucedido en el pasado, sabrán qué medir y tendrán más confianza para realizar cambios en el código.
rwong
29

En resumen: depende

  • ¿Realmente va a necesitar o usar su versión refactorizada / mejorada?

    • ¿Hay una ganancia concreta, inmediata o a largo plazo?
    • ¿Es esta ganancia solo para mantenimiento, o realmente arquitectónica?
  • ¿Realmente necesita ser optimizado?

    • ¿Por qué?
    • ¿A qué objetivo objetivo debes apuntar?

En detalles

¿Vas a necesitar las cosas limpias y brillantes?

Hay cosas de las que hay que ser cauteloso aquí, y debe identificar el límite entre lo que es real, la ganancia medible y lo que es solo su preferencia personal y el mal hábito potencial de tocar el código que no debería ser.

Más específicamente, sepa esto:

Existe una ingeniería excesiva

Es un antipatrón, y viene con problemas integrados:

  • que puede ser más extensible , pero no puede ser más fácil de extender,
  • que no puede ser más sencillo de entender ,
  • Por último, pero definitivamente no menos importante aquí: puede ralentizar todo el código.

Algunos también podrían mencionar el principio KISS como referencia, pero aquí es contra-intuitivo: ¿es la forma optimizada la forma simple o la forma de arquitectura limpia? La respuesta no es necesariamente absoluta, como se explica en el resto a continuación.

No lo vas a necesitar

El principio YAGNI no es completamente ortogonal con el otro problema, pero ayuda a hacerse la pregunta: ¿lo va a necesitar?

¿La arquitectura más compleja realmente presenta un beneficio para usted, además de dar la apariencia de ser más sostenible?

Si no se rompió, no lo arregles

Escriba esto en un póster grande y cuélguelo junto a su pantalla o en el área de la cocina en el trabajo, o en la sala de reuniones de desarrollo. Por supuesto, hay muchos otros mantras que vale la pena repetir, pero este en particular es importante cada vez que intentas hacer un "trabajo de mantenimiento" y sientes la necesidad de "mejorarlo".

Es natural para nosotros querer "mejorar" el código o incluso simplemente tocarlo, incluso inconscientemente, mientras lo leemos para tratar de entenderlo. Es algo bueno, ya que significa que somos obstinados e intentamos obtener una comprensión más profunda de los aspectos internos, pero también está vinculado a nuestro nivel de habilidad, nuestro conocimiento (¿cómo decide qué es mejor o no? Bueno, consulte las secciones a continuación) ...), y todas las suposiciones que hacemos sobre lo que creemos que conocemos el software ...:

  • en realidad lo hace,
  • en realidad necesita hacer,
  • eventualmente tendrá que hacer,
  • y qué tan bien lo hace.

¿Realmente necesita ser optimizado?

Dicho todo esto, ¿por qué se "optimizó" en primer lugar? Dicen que la optimización prematura es la raíz de todo mal, y si ve un código indocumentado y aparentemente optimizado, por lo general, podría suponer que probablemente no siguió las Reglas de optimización que realmente no necesitaba el esfuerzo de optimización y que era solo el la arrogancia habitual de los desarrolladores está empezando. Una vez más, tal vez sea solo tuyo hablando ahora.

Si es así, ¿dentro de qué límites se vuelve aceptable? Si es necesario, este límite existe y le da espacio para mejorar las cosas, o una línea dura para decidir dejarlo ir.

Además, tenga cuidado con las características invisibles. Lo más probable es que su versión "extensible" de este código también acumule más memoria en tiempo de ejecución y presente incluso una mayor huella de memoria estática para el ejecutable. Las características brillantes de OO vienen con costos poco intuitivos como estos, y pueden ser importantes para su programa y el entorno en el que se supone que se ejecuta.

Medida, medida, medida

Como la gente de Google ahora, ¡se trata de datos! Si puede hacer una copia de seguridad con datos, entonces es necesario.

Hay una historia no tan antigua que por cada $ 1 gastado en desarrollo será seguido por al menos $ 1 en pruebas y al menos $ 1 en soporte (pero en realidad, es mucho más).

El cambio impacta muchas cosas:

  • es posible que deba producir una nueva compilación;
  • debe escribir nuevas pruebas unitarias (definitivamente si no hubiera ninguna, y su arquitectura más extensible probablemente deja espacio para más, ya que tiene más superficie para errores);
  • debe escribir nuevas pruebas de rendimiento (para asegurarse de que esto se mantenga estable en el futuro y para ver dónde están los cuellos de botella), y esto es difícil de hacer ;
  • necesitará documentarlo (y más extensible significa más espacio para detalles);
  • usted (u otra persona) deberá volver a probarlo exhaustivamente en QA;
  • el código (casi) nunca está libre de errores, y deberá admitirlo.

Por lo tanto, no es solo el consumo de recursos de hardware (velocidad de ejecución o huella de memoria) lo que necesita medir aquí, sino también el consumo de recursos del equipo . Ambos deben predecirse para definir un objetivo objetivo, medirse, contabilizarse y adaptarse según el desarrollo.

Y para su gerente, eso significa adaptarlo al plan de desarrollo actual, así que comuníquese al respecto y no entre en la furiosa codificación cow-boy / submarine / black-ops.


En general...

Sí, pero...

No me malinterpretes, en general, estaría a favor de hacer por qué sugieres, y a menudo lo defiendo. Pero debe ser consciente del costo a largo plazo.

En un mundo perfecto, es la solución correcta:

  • el hardware de la computadora mejora con el tiempo,
  • los compiladores y las plataformas de tiempo de ejecución mejoran con el tiempo,
  • obtienes código casi perfecto, limpio, fácil de mantener y legible.

En la práctica:

  • puedes empeorarlo

    Necesitas más globos oculares para mirarlo, y cuanto más lo compliques, más globos oculares necesitarás.

  • no puedes predecir el futuro

    No puede saber con absoluta certeza si alguna vez lo necesitará y ni siquiera si las "extensiones" que necesitaría hubieran sido más fáciles y rápidas de implementar en la forma anterior, y si ellos mismos tendrían que estar súper optimizados .

  • representa, desde la perspectiva de la gerencia, un gran costo sin ganancia directa.

Hazlo parte del proceso

Menciona aquí que es un cambio bastante pequeño, y tiene algunos problemas específicos en mente. Yo diría que generalmente está bien en este caso, pero la mayoría de nosotros también tenemos historias personales de pequeños cambios, ediciones casi quirúrgicas, que eventualmente se convirtieron en pesadillas de mantenimiento y plazos casi vencidos o explotados porque Joe Programmer no vio uno. de las razones detrás del código y tocó algo que no debería haber sido.

Si tiene un proceso para manejar tales decisiones, les quita la ventaja personal:

  • Si prueba las cosas correctamente, sabrá más rápido si las cosas están rotas,
  • Si los mide, sabrá si mejoraron,
  • Si lo revisas, sabrás si ahuyenta a las personas.

La cobertura de prueba, la creación de perfiles y la recopilación de datos son difíciles

Pero, por supuesto, su código de prueba y sus métricas pueden sufrir los mismos problemas que está tratando de evitar para su código real: prueba las cosas correctas, y son lo correcto para el futuro, y mide lo correcto ¿cosas?

Aún así, en general, cuanto más pruebas (hasta cierto límite) y midas, más datos recolectas y más seguro estarás. Mal tiempo de analogía: piense en ello como conducir (o la vida en general): puede ser el mejor conductor del mundo, si el automóvil se descompone o alguien decide suicidarse al conducir con su propio automóvil hoy, su Las habilidades pueden no ser suficientes. Hay dos cosas ambientales que pueden afectarlo, y los errores humanos también importan.

Las revisiones de código son las pruebas de pasillo del equipo de desarrollo

Y creo que la última parte es clave aquí: hacer revisiones de código. No sabrás el valor de tus mejoras si las haces solo. Las revisiones de código son nuestras "pruebas de pasillo": siga la versión de Raymond de la Ley de Linus tanto para detectar errores como para detectar sobre ingeniería y otros antipatrones, y para asegurarse de que el código esté en línea con las habilidades de su equipo. No tiene sentido tener el "mejor" código si nadie más puede entenderlo y mantenerlo, y eso se aplica tanto a las optimizaciones crípticas como a los diseños arquitectónicos profundos de 6 capas.

Como palabras de cierre, recuerde:

Todo el mundo sabe que la depuración es el doble de difícil que escribir un programa en primer lugar. Entonces, si eres tan inteligente como puedes ser cuando lo escribes, ¿cómo lo vas a depurar? - Brian Kernighan

revs haylem
fuente
"Si no está roto, no lo arregles" va en contra de la refactorización. No importa si algo funciona, si no se puede mantener, debe cambiarse.
Miyamoto Akira
@MiyamotoAkira: es una cosa de dos velocidades. Si no está roto pero es aceptable y es menos probable que vea apoyo, podría ser aceptable dejarlo solo en lugar de introducir nuevos errores potenciales o pasar tiempo de desarrollo en él. Se trata de evaluar el beneficio, tanto a corto como a largo plazo, de la refactorización. No hay una respuesta clara, requiere alguna evaluación.
haylem
convenido. Supongo que no me gusta la oración (y la filosofía detrás de ella) porque veo la refactorización como la opción predeterminada, y solo si parece que va a tomar demasiado tiempo o es muy difícil, entonces / debería decidirse no ve con eso. Eso sí, he sido quemado por personas que no cambiaron las cosas, que aunque funcionaban, eran claramente la solución incorrecta tan pronto como tenía que mantenerlas o ampliarlas.
Miyamoto Akira
@MiyamotoAkira: las oraciones cortas y las declaraciones de opiniones no pueden expresar mucho. Están destinados a estar en tu cara y a desarrollarse a un lado, supongo. Estoy muy interesado en revisar y tocar el código con la mayor frecuencia posible, incluso si a menudo no tengo una gran red de seguridad o sin mucha razón. Si está sucio, lo limpias. Pero, de manera similar, también me quemé varias veces. Y todavía se quemará. Mientras no sean de tercer grado, no me importa mucho, hasta ahora siempre fueron quemaduras a corto plazo para ganancias a largo plazo.
haylem
8

En general, primero debe centrarse en la legibilidad y el rendimiento mucho más tarde. La mayoría de las veces, esas optimizaciones de rendimiento son insignificantes de todos modos, pero el costo de mantenimiento puede ser enorme.

Ciertamente, todas las cosas "pequeñas" deberían cambiarse a favor de la claridad ya que, como usted señaló, la mayoría de ellas serán optimizadas por el compilador de todos modos.

En cuanto a las optimizaciones más grandes, puede haber una posibilidad de que las optimizaciones sean realmente críticas para alcanzar un rendimiento razonable (aunque este no es el caso sorprendentemente a menudo). Haría sus cambios y luego perfilaría el código antes y después de los cambios. Si el nuevo código tiene problemas de rendimiento significativos, siempre puede retroceder a la versión optimizada, y si no, puede quedarse con la versión de código más limpia.

Cambie solo una parte del código a la vez y vea cómo afecta el rendimiento después de cada ronda de refactorización.

Oleksi
fuente
8

Depende de por qué se optimizó el código y cuál sería el efecto de cambiarlo y cuál podría ser el impacto del código en el rendimiento general. También debe depender de si tiene una buena manera de cargar los cambios de prueba.

No debe realizar este cambio sin perfilar antes y después y preferiblemente bajo una carga similar a la que se vería en la producción. Eso significa no usar un pequeño subconjunto de datos en una máquina desarrolladora o realizar pruebas cuando solo un usuario está usando el sistema.

Si la optimización fue reciente, es posible que pueda hablar con el desarrollador y averiguar exactamente cuál era el problema y qué tan lenta era la aplicación antes de la optimización. Esto puede decirle mucho sobre si vale la pena hacer la optimización y para qué condiciones se necesitaba la optimización (un informe que cubre todo un año, por ejemplo, puede no haberse retrasado hasta septiembre u octubre, si está probando su cambio en febrero, la lentitud podría no ser evidente y la prueba no válida).

Si la optimización es bastante antigua, los métodos más nuevos podrían incluso ser más rápidos y más legibles.

En última instancia, esta es una pregunta para su jefe. Lleva mucho tiempo refactorizar algo que ha sido optimizado y asegurarse de que el cambio no haya afectado el resultado final y que funcione tan bien o al menos aceptablemente en comparación con la forma anterior. Es posible que desee que pase su tiempo en otras áreas en lugar de asumir una tarea de alto riesgo para ahorrar unos minutos de tiempo de codificación. O puede estar de acuerdo en que el código es difícil de entender y ha necesitado una intervención frecuente y que ahora hay mejores métodos disponibles.

HLGEM
fuente
6

si el perfil muestra que la optimización no es necesaria (no está en una sección crítica) o incluso tiene un tiempo de ejecución peor (como resultado de una mala optimización prematura), entonces reemplace con el código legible que es más fácil de mantener

también asegúrese de que el código se comporte igual con las pruebas apropiadas

2 revoluciones
fuente
5

Piénselo desde una perspectiva comercial. ¿Cuáles son los costos del cambio? ¿Cuánto tiempo necesita para hacer el cambio y cuánto ahorrará a largo plazo al hacer que el código sea más fácil de extender o mantener? Ahora adjunte una etiqueta de precio a ese momento y compárelo con el dinero perdido al reducir el rendimiento. Tal vez necesite agregar o actualizar un servidor para compensar el rendimiento perdido. Tal vez el producto ya no cumple con los requisitos y ya no se puede vender. Tal vez no hay pérdida. Quizás el cambio aumenta la robustez y ahorra tiempo en otros lugares. Ahora toma tu decisión.

Como nota al margen, en algunos casos podría ser posible mantener ambas versiones de un fragmento. Puede escribir una prueba generando valores de entrada aleatorios y verificar los resultados con la otra versión. Use la solución "inteligente" para verificar el resultado de una solución perfectamente comprensible y obviamente correcta y, por lo tanto, obtenga cierta seguridad (pero no prueba) de que la nueva solución es equivalente a la anterior. O vaya al revés y verifique el resultado del código complicado con el código detallado y, por lo tanto, documente la intención detrás del hack de manera inequívoca.

Scarfridge
fuente
4

Básicamente, se pregunta si la refactorización es una empresa que vale la pena. La respuesta a esto es ciertamente sí.

Pero...

... necesitas hacerlo con cuidado. Necesita pruebas sólidas de unidad, integración, funcionamiento y rendimiento para cualquier código que esté refactorizando. Debe estar seguro de que realmente prueban toda la funcionalidad requerida. Necesita la capacidad de ejecutarlos de manera fácil y repetida. Una vez que tenga eso, debería poder reemplazar los componentes con componentes nuevos que contengan la funcionalidad equivalente.

Martin Fowler escribió el libro sobre esto.

Matthew Flynn
fuente
3

No debe cambiar el código de trabajo y producción sin una buena razón. "Refactorizar" no es una razón suficiente a menos que no pueda hacer su trabajo sin esa refactorización. Incluso si lo que está haciendo es corregir errores dentro del código difícil en sí, debe tomarse el tiempo para comprenderlo y realizar el menor cambio posible. Si el código es tan difícil de entender, no podrá comprenderlo por completo, por lo que cualquier cambio que realice tendrá efectos secundarios impredecibles: errores, en otras palabras. Cuanto mayor sea el cambio, más probabilidades hay de que cause problemas.

Habría una excepción a esto: si el código incomprensible tuviera un conjunto completo de pruebas unitarias, podría refactorizarlo. Como nunca he visto ni oído hablar de un código incomprensible con pruebas unitarias completas, primero debe escribir las pruebas unitarias, obtener el acuerdo de las personas necesarias de que esas pruebas unitarias representan lo que debería hacer el código, y ENTONCES hacer que el código cambie . Lo he hecho una o dos veces; Es un dolor en el cuello y muy costoso, pero al final produce buenos resultados.

mjfgates
fuente
3

Si se trata de un código corto que hace algo relativamente simple de una manera difícil de entender, cambiaría la "comprensión rápida" en un comentario extendido y / o una implementación alternativa no utilizada, como

#ifdef READABLE_ALT_IMPLEMENTATION

   double x=0;
   for(double n: summands)
     x += n;
   return x;

#else

   auto subsum = [&](int lb, int rb){
          double x=0;
          while(lb<rb)
            x += summands[lb++];
          return x;
        };
   double x_fin=0;
   for(double nsm: par_eval( subsum
                           , partitions(n_threads, 0, summands.size()) ) )
     x_fin += nsm;
   return x_fin;

#endif
a la izquierda
fuente
3

La respuesta es, sin pérdida de generalidad, sí. Siempre agregue código moderno cuando vea un código difícil de leer y elimine el código incorrecto en la mayoría de los casos. Yo uso el siguiente proceso:

  1. Busque la prueba de rendimiento y la información de perfil de apoyo. Si no hay una prueba de rendimiento, lo que se puede afirmar sin evidencia se puede descartar sin evidencia. Afirma que tu código moderno es más rápido y elimina el código anterior. Si alguien discute (incluso usted mismo) pídales que escriban el código de perfil para probar cuál es más rápido.
  2. Si el código de perfil existe, escriba el código moderno de todos modos. Nómbralo como algo así <function>_clean(). Luego, "compita" su código contra el código incorrecto. Si su código es mejor, elimine el código anterior.
  3. Si el código anterior es más rápido, deje su código moderno allí de todos modos. Sirve como una buena documentación de lo que debe hacer el otro código, y dado que el código de "carrera" está allí, puede seguir ejecutándolo para documentar las características de rendimiento y las diferencias entre las dos rutas. También puede realizar pruebas unitarias para detectar diferencias en el comportamiento del código. Es importante destacar que el código moderno superará el código "optimizado" algún día, garantizado. Luego puede eliminar el código incorrecto.

QED

Sunny Kalsi
fuente
3

Si pudiera enseñarle al mundo una cosa (sobre Software) antes de morir, le enseñaría que "Rendimiento versus X" es un falso dilema.

La refactorización generalmente se conoce como una bendición para la legibilidad y la confiabilidad, pero puede soportar fácilmente la optimización. Cuando maneja la mejora del rendimiento como una serie de refactorizaciones, puede honrar la Regla del campamento y al mismo tiempo hacer que la aplicación vaya más rápido. En realidad, al menos en mi opinión, es éticamente obligatorio que lo haga.

Por ejemplo, el autor de esta pregunta ha encontrado un código loco. Si esta persona estuviera leyendo mi código, descubrirían que la parte loca tiene 3-4 líneas de largo. Está en un método en sí mismo, y el nombre y la descripción del método indican QUÉ está haciendo el método. El método contendría 2-6 líneas de comentarios en línea que describen CÓMO el código loco obtiene la respuesta correcta, a pesar de su apariencia cuestionable.

Compartimentado de esta manera, puede intercambiar implementaciones de este método como desee. De hecho, probablemente es así como escribí la versión loca para empezar. Puede probar, o al menos preguntar sobre alternativas. La mayoría de las veces descubrirá que la implementación ingenua es notablemente peor (generalmente solo me molesto en una mejora de 2 a 10 veces), pero los compiladores y las bibliotecas siempre están cambiando, y quién sabe lo que puede encontrar hoy que no estaba disponible cuando la función fue escrita?

Jason
fuente
Una clave importante para la eficiencia en muchos casos es hacer que un código haga el mayor trabajo posible de manera que se pueda hacer de manera eficiente. Una de las cosas que me molesta con .NET es que no hay mecanismos eficientes para, por ejemplo, copiar parte de una colección a otra. La mayoría de las colecciones almacenan grandes grupos de artículos consecutivos (si no todo) en matrices, por lo que, por ejemplo, copiar los últimos 5,000 artículos de una lista de 50,000 artículos debe descomponerse en algunas operaciones de copia masiva (si no solo una) más algunas otras pasos realizados como máximo un puñado de veces cada uno.
supercat
Desafortunadamente, incluso en los casos en que debería ser posible realizar tales operaciones de manera eficiente, a menudo será necesario ejecutar bucles "voluminosos" durante 5,000 iteraciones (¡y en algunos casos 45,000!). Si una operación se puede reducir a cosas como las copias de matriz masiva, entonces se pueden optimizar en grados extremos produciendo una recompensa importante. Si cada iteración de bucle necesita hacer una docena de pasos, es difícil optimizar alguno de ellos particularmente bien.
supercat
2

Probablemente no sea una buena idea tocarlo: si el código se escribió de esa manera por razones de rendimiento, significa que cambiarlo podría traer problemas de rendimiento que se habían resuelto previamente.

Si no decide cambiar las cosas sean más legibles y extensible: Antes de realizar un cambio, el código de referencia de edad bajo pesada carga. Aún mejor si puede encontrar un documento antiguo o un ticket de problema que describa el problema de rendimiento que se supone que este código de aspecto extraño debe solucionar. Luego, después de realizar los cambios, vuelva a ejecutar las pruebas de rendimiento. Si no es muy diferente, o aún está dentro de los parámetros aceptables, entonces probablemente esté bien.

A veces puede suceder que cuando otras partes de un sistema cambian, este código optimizado para el rendimiento ya no necesita optimizaciones tan pesadas, pero no hay forma de saberlo con certeza sin pruebas rigurosas.

FrustratedWithFormsDesigner
fuente
1
Uno de los tipos con los que trabajo ahora adora optimizar las cosas en áreas que los usuarios visitan una vez al mes, si es que a menudo. Lleva tiempo y, con frecuencia, causa otros problemas porque le gusta codificar y confirmar, y deja que el control de calidad u otra función posterior realmente se pruebe. : / Para ser justos, en general es rápido, rápido y preciso, pero estas "optimizaciones" de centavo hacen que las cosas sean más difíciles para el resto del equipo y sus muertes permanentes serían algo bueno.
DaveE
@DaveE: ¿Estas optimizaciones se aplican debido a problemas de rendimiento reales? ¿O este desarrollador lo hace solo porque puede? Supongo que si sabes que las optimizaciones no van a tener un impacto, puedes reemplazarlas de manera segura con un código más legible, pero solo confiaría en alguien que sea un experto en el sistema para hacer eso.
FrustratedWithFormsDesigner
Han terminado porque él puede. En realidad, generalmente guarda algunos ciclos, pero cuando la interacción del usuario con el elemento del programa toma una cierta cantidad de segundos (15 a 300-ish), reducir una décima de segundo de tiempo de ejecución en busca de "eficiencia" es una tontería. Especialmente cuando la gente que lo sigue tiene que tomarse un tiempo real para entender lo que hizo. Esta es una aplicación PowerBuilder creada originalmente hace 16 años, por lo que dada la génesis de las cosas, la mentalidad es quizás comprensible, pero se niega a actualizar su mentalidad a la realidad actual.
DaveE
@DaveE: Creo que estoy más de acuerdo con el tipo con el que trabajas que contigo. Si no se me permite arreglar cosas que son lentas sin ninguna razón, me volveré loco. Si veo una línea de C ++ que usa repetidamente el operador + para ensamblar una cadena, o un código que se abre y lee / dev / urandom cada vez a través del ciclo solo porque alguien olvidó establecer una bandera, entonces lo arreglo. Al ser fanático de esto, he logrado mantener la velocidad, cuando otras personas lo habrían dejado pasar un microsegundo a la vez.
Zan Lynx
1
Bueno, tendremos que aceptar estar en desacuerdo. Pasar una hora cambiando algo para ahorrar segundos fraccionarios en tiempo de ejecución para una función que se ejecuta realmente ocasionalmente y dejar el código en forma de rascarse la cabeza para los otros desarrolladores ... no está bien. Si estas fueron funciones que se ejecutaron repetidamente en partes de la aplicación de alto estrés, bien y elegante. Pero ese no es el caso que estoy describiendo. Esto es una codificación de código verdaderamente gratuita por la única razón de decir "Hice esto cosa que UserX hace una vez por semana fraccionalmente más rápido". Mientras tanto, tenemos que pagar el trabajo que hay que hacer.
DaveE
2

El problema aquí es distinguir "optimizado" de legible y extensible, lo que nosotros como usuarios vemos como código optimizado y lo que el compilador ve como optimizado son dos cosas diferentes. El código que está buscando cambiar podría no ser un cuello de botella y, por lo tanto, incluso si el código es "sencillo", ni siquiera es necesario que esté "optimizado". O si el código es lo suficientemente antiguo, puede haber optimizaciones hechas por el compilador a las incorporaciones que hacen que el uso de una estructura incorporada simple más nueva sea igual o más eficiente que el código anterior.

Y el código "magra" ilegible no siempre está optimizado.

Solía ​​tener la mentalidad de que el código inteligente / esbelto era un buen código, pero a veces aprovechando las reglas oscuras del lenguaje que dañan en lugar de ayudar en la creación del código, me han mordido más a menudo que en cualquier trabajo incrustado al intentar sea ​​inteligente porque el compilador convierte su código inteligente en algo completamente inutilizable por el hardware incorporado.

Jeff Langemeier
fuente
2

Nunca reemplazaré el código optimizado con código legible porque no puedo comprometer el rendimiento y optaré por utilizar los comentarios adecuados en todas y cada una de las secciones para que cualquiera pueda comprender la lógica implementada en esa sección optimizada que resolverá ambos problemas.

Por lo tanto, el código será optimizado + los comentarios adecuados también lo harán legible.

NOTA: Puede hacer que un Código optimizado sea legible con la ayuda de comentarios apropiados, pero no puede hacer que el Código legible sea optimizado.

Logicalj
fuente
Estaría cansado de este enfoque, ya que todo lo que se necesita es que una persona edite el código para olvidar mantener el comentario sincronizado. De repente, cada revisión posterior se irá pensando que realiza X mientras realmente hace Y.
John D
2

Aquí hay un ejemplo para ver la diferencia entre código simple y código optimizado: https://stackoverflow.com/a/11227902/1396264

hacia el final de la respuesta simplemente reemplaza:

if (data[c] >= 128)
    sum += data[c];

con:

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];

Para ser justos, no tengo idea de con qué se ha reemplazado la declaración if, pero como dice el respondedor, sus operaciones bit a bit dan el mismo resultado (solo voy a tomar su palabra) .

Esto se ejecuta en menos de una cuarta parte del tiempo original (11.54 segundos frente a 2.5 segundos)

revs vedant1811
fuente
1

La pregunta principal aquí es: ¿se requiere la optimización?

Si es así, no puede reemplazarlo con un código más lento y legible. Deberá agregar comentarios, etc. para hacerlo más legible.

Si el código no tiene que ser optimizado, entonces no debería serlo (hasta el punto de afectar la legibilidad) y puede re-factorizarlo para hacerlo más legible.

SIN EMBARGO: asegúrese de saber exactamente qué hace el código y cómo probarlo a fondo antes de comenzar a cambiar las cosas. Esto incluye el uso pico, etc. Si no tiene que componer un conjunto de casos de prueba y ejecutarlos antes y después, entonces no tiene tiempo para refactorizar.

Stefan
fuente
1

Así es como hago las cosas: primero lo hago funcionar en código legible, luego lo optimizo. Mantengo la fuente original y documento mis pasos de optimización.

Luego, cuando necesito agregar una función, vuelvo a mi código legible, agrego la función y sigo los pasos de optimización que documenté. Debido a que documentaste, es muy rápido y fácil reoptimizar tu código con la nueva función.

Pieter B
fuente
0

La legibilidad de la OMI es más importante que el código optimizado porque en la mayoría de los casos la microoptimización no vale la pena.

Artículo sobre micro optimizaciones sin sentido :

Como la mayoría de nosotros, estoy cansado de leer publicaciones de blog sobre micro optimizaciones sin sentido, como reemplazar print por echo, ++ $ i por $ i ++, o comillas dobles por comillas simples. ¿Por qué? Porque 99.999999% del tiempo, es irrelevante.

"print" usa un código de operación más que "echo" porque en realidad devuelve algo. Podemos concluir que el eco es más rápido que la impresión. Pero un código de operación no cuesta nada, realmente nada.

He intentado en una nueva instalación de WordPress. El script se detiene antes de que termine con un "Error de bus" en mi computadora portátil, pero el número de códigos de operación ya era de más de 2.3 millones. Basta de charla.

webvitaly
fuente
0

La optimización es relativa. Por ejemplo:

Considere una clase con un grupo de miembros de BOOL:

// no nitpicking over BOOL vs bool allowed
class Pear {
 ...
 BOOL m_peeled;
 BOOL m_sliced;
 BOOL m_pitted;
 BOOL m_rotten;
 ...
};

Puede sentirse tentado a convertir los campos BOOL en campos de bits:

class Pear {
 ...
 BOOL m_peeled:1;
 BOOL m_sliced:1;
 BOOL m_pitted:1;
 BOOL m_rotten:1;
 ...
};

Dado que un BOOL se define como INT (que en las plataformas de Windows es un entero de 32 bits con signo), esto toma dieciséis bytes y los agrupa en uno. ¡Eso es un 93% de ahorro! ¿Quién podría quejarse de eso?

Esta suposición:

Dado que un BOOL se define como INT (que en las plataformas de Windows es un entero de 32 bits con signo), esto toma dieciséis bytes y los agrupa en uno. ¡Eso es un 93% de ahorro! ¿Quién podría quejarse de eso?

lleva a:

La conversión de un BOOL a un campo de un solo bit ahorró tres bytes de datos, pero le costó ocho bytes de código cuando se le asigna al miembro un valor no constante. Del mismo modo, extraer el valor se vuelve más caro.

Lo que solía ser

 push [ebx+01Ch]      ; m_sliced
 call _Something@4    ; Something(m_sliced);

se convierte

 mov  ecx, [ebx+01Ch] ; load bitfield value
 shl  ecx, 30         ; put bit at top
 sar  ecx, 31         ; move down and sign extend
 push ecx
 call _Something@4    ; Something(m_sliced);

La versión de bitfield es más grande en nueve bytes.

Sentémonos y hagamos algo de aritmética. Suponga que se accede a cada uno de estos campos con campo de bits seis veces en su código, tres veces para escribir y tres veces para leer. El costo en el crecimiento del código es de aproximadamente 100 bytes. No será exactamente 102 bytes porque el optimizador puede aprovechar los valores que ya están en los registros para algunas operaciones, y las instrucciones adicionales pueden tener costos ocultos en términos de flexibilidad de registro reducida. La diferencia real puede ser mayor, puede ser menor, pero para un cálculo al final de la lista llamemos 100. Mientras tanto, el ahorro de memoria fue de 15 bytes por clase. Por lo tanto, el punto de equilibrio es siete. Si su programa crea menos de siete instancias de esta clase, entonces el costo del código excede el ahorro de datos: su optimización de memoria fue una des-optimización de memoria.

Referencias

Paul Sweatte
fuente