Durante mucho tiempo en lugares como el canal IRC de Java , SO y otros lugares, me han dicho algo como "Preocúpate por cómo se ve el código y su legibilidad / comprensibilidad ahora, y el rendimiento más tarde si es absolutamente necesario". Por lo tanto, durante mucho tiempo, realmente no he tenido un TOC sobre el rendimiento de mis pequeñas aplicaciones de escritorio o web, solo eliminé lo obviamente ineficiente.
La mayoría de las respuestas son "¿Qué pasa con la escalabilidad?". Ese es un punto legítimo, pero si mi aplicación solo se creó para analizar, digamos, archivos de 10,000 líneas de largo, ¿debería hacer que mi código sea un desastre para el pequeño porcentaje de personas que van a meter un archivo de 1,000,000 de líneas?
Mi pregunta principal es ¿cuándo debería cambiar las formas fáciles pero algo ineficientes de hacer tareas para grandes bestias gigantes y complicadas que hacen las cosas extremadamente rápido pero destruyen las posibles formas de actualización y hacen que el código sea excesivamente difícil y propenso a ser reescrito por el próximo desarrollador?
fuente
Esto suele ser una dicotomía falsa . Puede escribir código maravillosamente eficiente, legible y mantenible. Puedes escribir montones de desorden maravillosamente ineficientes e imposibles de mantener.
Cuando trato con problemas de rendimiento, generalmente trato de pensar en el problema comercial que estoy resolviendo. ¿Cómo se comportará mi software cuando mis clientes lo usen? ¿El rendimiento de mis aplicaciones hará feliz a Jacob Nielsen ?
fuente
Una verdad que aprendí estudiando microprocesadores en la universidad que se quedó conmigo: "Haga el caso común rápido. Haga que el caso poco común sea correcto".
Mientras tenga solo un pequeño porcentaje de usuarios que ahogan su código con una entrada de dos órdenes de magnitud más grande de lo que estaba destinado a manejar, no se preocupe. Asegúrate de que maneja la entrada correctamente si la dan durante el tiempo suficiente y no deja nada corrupto en inutilidad si mata el trabajo antes de que termine.
Pero, una vez más y más personas comienzan a usarlo de esa manera (o comienzan a decirte "Sabes, me encantaría usar esa herramienta que escribiste en mis informes semanales de TPS, pero toma todo el día"), ahí es cuando comienzas a considerar cambiar la facilidad de mantenimiento por ganancias de rendimiento.
fuente
"Preocuparse por el aspecto del código y su legibilidad / comprensibilidad ahora, y el rendimiento más adelante si es absolutamente necesario" es la salida fácil, y generalmente inútil. Un buen diseño será fácil de mantener, fácil de leer y eficiente.
El rendimiento es un componente común de un buen diseño. Si su programa es lento y derrochador, realmente no es reutilizable. cuando necesita arreglar ese desastre, fuerza las actualizaciones en sus clientes, a menos que sea demasiado tiempo para que ellos actualicen. ese programa lento se convierte en el gran desastre que es demasiado costoso para mejorar. luego eligen una alternativa porque no se ajusta a sus necesidades. El diagnóstico, la actualización y el tratamiento de los efectos secundarios de las mejoras en un mal diseño a menudo superan el tiempo de desarrollo inicial de escribirlo para que sea eficiente, funcione correctamente y tenga un diseño generalmente bueno. ese programa es altamente reutilizable y requiere poco mantenimiento (win).
por lo tanto, la respuesta corta a su pregunta es "no sea un desperdicio. Escriba para reutilizar. Está bien ser flojo al crear prototipos / desarrollar pruebas de conceptos, pero no use ese prototipo para el código de producción".
tenga en cuenta y evite diseños innecesarios al escribir programas de producción y programas que pretende reutilizar. durante la implementación es el momento ideal para escribir su programa para que no sea un desperdicio: tiene una idea clara de los detalles y su funcionamiento, y es realmente doloroso e ineficaz solucionarlo después de que se haya escrito. mucha gente cree un poco de elaboración de perfiles (tal vez) al final o si hay un problema es un adequete, cuando generalmente es demasiado tiempo para rediseñar / cambiar y las ineficiencias son tantas y tan generalizadas que no comprende el programa bien basado en los resultados de un perfil. Este enfoque lleva poco tiempo durante la implementación y (suponiendo que lo haya hecho suficientes veces) generalmente da como resultado un diseño que es varias veces más rápido y es reutilizable en muchos más contextos. no ser derrochador elegir buenos algoritmos, pensar en sus implementaciones y reutilizar las implementaciones correctas son todos componentes de un buen diseño; todos los cualesmejora la legibilidad, la mantenibilidad y la reutilización con más frecuencia de lo que perjudica.
fuente
Trato de hacer que el código sea legible: el rendimiento está condenado
Cuando, y si, el código resulta ser demasiado lento, lo refactorizaré para que sea más rápido. Por lo general, el proceso de refactorización se sigue con muchos comentarios, ya que el código tiende a ser menos legible.
fuente
¿Nunca?
En serio, el código siempre debe escribirse para que sea fácil de entender y mantener.
Con respecto a cuándo lidiar con los problemas de rendimiento, trátelos una vez que los identifique, no optimice previamente su código porque entonces solo estará adivinando dónde están los problemas de rendimiento.
Si su código está escrito para que sea claro, conciso, comprensible y fácil de mantener, usted u otro programador no deberían tener problemas para refactorizar el código para hacerlo más eficiente.
fuente
Normalmente escribo código para ser legible en primer lugar. Si, y solo si, encuentro que el programa se ejecuta demasiado lento para hacer su trabajo, perfilo y optimizo. Dicho esto, no hay nada de malo en adquirir el hábito de realizar optimizaciones comunes que no afecten la legibilidad de su código. Es decir, si un código puede escribirse de dos maneras legibles (o casi igualmente), elija la que sea más rápida.
Por ejemplo, en Python, las comprensiones de listas (o expresiones generadoras) tienden a ser más rápidas que el
for
bucle equivalente , por lo que utilizo las comprensiones de listas donde puedo, si no afectan la legibilidad (por ejemplo, no anido las comprensiones de listas si Puedo evitarlo y usar un bucle for porque las comprensiones de listas anidadas pueden ser difíciles de analizar mentalmente).Del mismo modo, los tipos de datos inmutables tienden a ser más rápidos que los mutables, por lo que uso tipos de datos inmutables donde puedo.
fuente
Si está trabajando en áreas realmente críticas para el rendimiento, entonces no puede posponer la eficiencia como una ocurrencia tardía. Es una de las cosas más importantes en las que pensar cuando se diseña desde el principio en esos casos y en formas que se relacionan con la mantenibilidad del resultado final.
No puede diseñar e implementar un servidor a gran escala y simplemente comenzar a escribir código fácil y bien documentado que solo utiliza funciones de bloqueo para todo con un bloqueo global de subprocesos que bloquea todo el sistema para procesar cada solicitud individual del cliente sin poner ningún pensado en estado compartido, contención de hilos y asincronía. Tal es una receta para el desastre y la necesidad de rediseñar y reescribir la mayor parte del código bien documentado que escribió de manera que podría conducir a la base de código más difícil de mantener imaginable, plagada de condiciones de carrera y puntos muertos como resultado de intentar para lograr la eficiencia requerida en retrospectiva, en lugar de haber pensado en diseños eficientes, simples y funcionales por adelantado.
Un equipo de desarrollo de juegos a los 8 meses de producción con un motor que solo pasa 2 cuadros por segundo en su hardware más robusto con 32 núcleos, mientras que tiene una tendencia a detenerse durante 15 segundos cada vez que la pantalla se llena, es poco probable que obtenga un producto utilizable de forma instantánea con solo arreglando un pequeño punto de acceso localizado. Lo más probable es que su diseño sea FUBAR de formas que justifiquen una revisión épica del tablero de dibujo y cambios de diseño que podrían caer en cascada en cada esquina de la base de código.
Con John Carmack, habló una vez sobre cómo una demostración tecnológica debe ejecutarse a un mínimo de cientos a miles de cuadros por segundo para integrarla en la producción. Esa no es una obsesión poco saludable con la eficiencia. Él sabe por adelantado que los juegos deben ejecutarse, en su totalidad, a más de 30 FPS para que los clientes lo encuentren aceptable. Como resultado, un pequeño aspecto como un sistema de sombra suave no puede ejecutarse a 30 FPS, o el juego en su conjunto no puede ser lo suficientemente rápido como para proporcionar la retroalimentación requerida en tiempo real. Es inutilizable hasta que logre la eficiencia requerida. En esas áreas críticas de rendimiento donde hay un requisito fundamental para la eficiencia, una solución que no logra alcanzar la velocidad adecuada en realidad no es mejor que una que no funciona en absoluto,. Y no puede diseñar un sistema eficiente de sombras suaves que se ejecute a cientos o miles de cuadros por segundo como se requiere para un motor de juego en tiempo real, a menos que ponga una cantidad predominante de pensamiento por adelantado en cuanto a su eficiencia. De hecho, en tales casos, más del 90% del trabajo está orientado a la eficiencia, ya que es trivial crear un sistema de sombra suave que funcione bien a las 2 horas por cuadro utilizando el trazado de ruta, pero no puede esperar ajustarlo. para correr a cientos de cuadros por segundo sin un cambio de enfoque totalmente diferente.
Cuando la eficiencia es una parte fundamental del diseño de una aplicación, no puede esperar lograr eficiencia en retrospectiva sin perder dramáticamente más tiempo del que ahorró al ignorarla, ya que no puede esperar lograr un diseño funcional en retrospectiva. Nadie dice: " está bien posponer pensar en el diseño hasta más tarde. Simplemente documente bien su código y podrá encontrar un diseño adecuado más adelante ". Pero en arquitecturas de rendimiento crítico, eso es lo que está haciendo efectivamente si no pone mucha atención y pensamiento en diseños eficientes por adelantado.
Ahora, eso no significa que deba microajustar sus implementaciones de inmediato. Para detalles de implementación, hay mucho espacio para iterar hacia soluciones más rápidas después de medir, siempre que el diseño no tenga que cambiar y, a menudo, esa es la forma más productiva de hacerlo. Pero a nivel de diseño, significa que debe pensar lo suficiente en cómo el diseño y la arquitectura se relacionarán con la eficiencia desde el principio.
La diferencia clave aquí es diseño.. No es fácil hacer grandes cambios en los diseños en retrospectiva, ya que los diseños acumulan dependencias, y las dependencias se romperán si el diseño cambia. Y si un diseño tiene el requisito de ser razonablemente eficiente o, en algunos casos, que su calidad se mide en gran medida por su eficiencia, entonces no debe esperar poder lograr un diseño adecuado como una ocurrencia tardía. Con cualquier producto competitivo donde la eficiencia es un gran aspecto de la calidad, ya sean sistemas operativos o compiladores o procesadores de video o raytracers o motores de juegos o motores de física, las reflexiones sobre la eficiencia y las representaciones de datos se pensaron meticulosamente desde el principio. Y en esos casos, no es una optimización prematura pensar tanto en la eficiencia por adelantado. Estaba colocando tal pensamiento exactamente en el momento más productivo para hacerlo,
fuente