Optimización de código: la forma óptima de optimizar
Publicado: 2022-03-11La optimización del rendimiento es una de las mayores amenazas para su código.
Usted puede estar pensando, no otra de esas personas . Entiendo. La optimización de cualquier tipo debería ser claramente algo bueno, a juzgar por su etimología, así que, naturalmente, querrás ser bueno en eso.
No solo para diferenciarse de la multitud como un mejor desarrollador. No solo para evitar ser "Dan" en The Daily WTF , sino porque cree que la optimización del código es lo correcto. Te enorgulleces de tu trabajo.
El hardware de la computadora se vuelve cada vez más rápido y el software es más fácil de hacer, pero cualquier cosa simple que solo quieras poder hacer, maldita sea, siempre lleva más tiempo que la anterior. Sacude la cabeza ante este fenómeno (por cierto, conocido como la Ley de Wirth) y decide oponerse a esa tendencia.
Eso es noble de tu parte, pero detente.
¡Solo para!
Está en grave peligro de frustrar sus propios objetivos, sin importar la experiencia que tenga en la programación.
¿Cómo es eso? Retrocedamos.
En primer lugar, ¿qué es la optimización de código?
A menudo, cuando lo definimos, asumimos que queremos que el código funcione mejor. Decimos que la optimización de código es escribir o reescribir código para que un programa utilice la menor cantidad posible de memoria o espacio en disco, minimice el tiempo de CPU o el ancho de banda de la red, o aproveche al máximo los núcleos adicionales.
En la práctica, a veces usamos otra definición por defecto: Escribir menos código.
Pero el código preventivo rudo que está escribiendo con ese objetivo es aún más probable que se convierta en una espina en el costado de alguien. ¿Cuyo? La próxima persona desafortunada que tiene que comprender su código, que incluso puede ser usted mismo. Y alguien inteligente y capaz, como usted, puede evitar el autosabotaje: mantenga sus fines nobles pero reevalúe sus medios, a pesar de que parecen ser incuestionablemente intuitivos.
Entonces, la optimización de código es un término un poco vago. Eso es incluso antes de que consideremos algunas de las otras formas en que se puede optimizar el código, que veremos a continuación.
Comencemos escuchando los consejos de los sabios mientras exploramos juntos las famosas reglas de optimización de código de Jackson:
- no lo hagas
- (¡Solo para expertos!) No lo hagas todavía .
1. No lo hagas: canalizar el perfeccionismo
Voy a comenzar con un ejemplo bastante vergonzosamente extremo de una época en la que, hace mucho tiempo, estaba empezando a mojarme los pies en el maravilloso mundo de SQL. El problema fue que luego pisé el pastel y no quise comerlo más porque estaba mojado y empezó a oler a pies.
Estaba empezando a mojarme los pies en el maravilloso mundo de SQL, donde puedes comer tu pastel y comerlo también. El problema fue que luego pisé el pastel...
Esperar. Permítanme salir de este accidente automovilístico de una metáfora que acabo de hacer y explicar.
Estaba haciendo I+D para una aplicación de intranet, que esperaba que algún día se convirtiera en un sistema de gestión completamente integrado para la pequeña empresa en la que trabajaba. Haría un seguimiento de todo por ellos y, a diferencia de su sistema actual, nunca perdería sus datos, porque estaría respaldado por un RDBMS, no por el archivo plano escamoso de cosecha propia que había usado otro desarrollador. Quería diseñar todo lo más inteligente posible desde el principio porque tenía una pizarra en blanco. Las ideas para este sistema explotaban como fuegos artificiales en mi mente, y comencé a diseñar tablas: contactos y sus muchas variaciones contextuales para un CRM, módulos de contabilidad, inventario, compras, CMS y gestión de proyectos, que pronto estaría probando.
Que todo se detuvo, en cuanto a desarrollo y rendimiento, debido a... lo adivinaste, optimización.
Vi que los objetos (representados como filas de tablas) podrían tener muchas relaciones diferentes entre sí en el mundo real y que podríamos beneficiarnos del seguimiento de estas relaciones: retendríamos más información y eventualmente podríamos automatizar el análisis comercial en todas partes. Viendo esto como un problema de ingeniería, hice algo que parecía una optimización de la flexibilidad del sistema.
En este punto, es importante cuidar tu cara, porque no me haré responsable si te duele la palma de la mano. ¿Listo? Creé dos tablas: relationship y una a la que tenía una referencia de clave externa, relationship_type . La relationship podría referirse a dos filas cualesquiera en cualquier parte de la base de datos completa y describir la naturaleza de la relación entre ellas.
Oh hombre. Acababa de optimizar esa flexibilidad muchísimo .
Demasiado, de hecho. Ahora tenía un nuevo problema: un tipo de relationship_type dado, naturalmente, no tendría sentido entre cada combinación dada de filas. Si bien podría tener sentido que una person tuviera una relación employed by con una company , eso nunca podría ser semánticamente equivalente a la relación entre, digamos, dos document .
Bueno, ningún problema. Simplemente agregaremos dos columnas a relationship_type , especificando a qué tablas se podría aplicar esta relación. (Puntos de bonificación aquí si adivina que pensé en normalizar esto moviendo esas dos columnas a una nueva tabla que hace referencia a relationship_type.id , de modo que las relaciones que podrían aplicarse semánticamente a más de un par de tablas no tendrían los nombres de las tablas duplicados. Después de todo, si necesitaba cambiar el nombre de una tabla y me olvidaba de actualizarlo en todas las filas correspondientes, ¡podría crear un error! En retrospectiva, al menos los errores habrían proporcionado alimento a las arañas que habitan en mi cráneo).
Afortunadamente, quedé inconsciente en una tormenta de pistas antes de viajar demasiado lejos por este camino. Cuando me desperté, me di cuenta de que había logrado, más o menos, volver a implementar las tablas internas relacionadas con la clave externa del RDBMS encima de sí mismo. Normalmente disfruto de los momentos que terminan cuando hago la proclamación fanfarrona de que "soy tan meta", pero este, desafortunadamente, no fue uno de ellos. Olvídese de no poder escalar : la horrenda hinchazón de este diseño hizo que el back-end de mi aplicación aún simple, cuya base de datos apenas estaba poblada con datos de prueba todavía, fuera casi inutilizable.
Retrocedamos por un segundo y echemos un vistazo a dos de las muchas métricas en juego aquí. Uno es la flexibilidad, que había sido mi objetivo declarado. En este caso, mi optimización, siendo de naturaleza arquitectónica, ni siquiera fue prematura:
(Hablaremos más de esto en mi artículo publicado recientemente, Cómo evitar la maldición de la optimización prematura). No obstante, mi solución fracasó espectacularmente por ser demasiado flexible. La otra métrica, la escalabilidad, era una que ni siquiera estaba considerando todavía, pero logré destruir al menos de manera espectacular con daños colaterales.
Así es, "Ay".
Esta fue una poderosa lección para mí sobre cómo la optimización puede salir completamente mal. Mi perfeccionismo implosionó por completo: mi inteligencia me había llevado a producir una de las soluciones más objetivamente poco inteligentes que jamás haya hecho.
Optimice sus hábitos, no su código
A medida que se dé cuenta de que tiende a refactorizar incluso antes de tener un prototipo funcional y un conjunto de pruebas para demostrar su corrección, considere dónde más puede canalizar este impulso. Sudoku y Mensa son geniales, pero tal vez algo que realmente beneficie directamente a su proyecto sería mejor:
- Seguridad
- Estabilidad en tiempo de ejecución
- Claridad y estilo
- Eficiencia de codificación
- Eficacia de la prueba
- perfilado
- Su caja de herramientas/DE
- SECO (No te repitas)
Pero tenga cuidado: la optimización de cualquiera de estos en particular tendrá el costo de los demás. Por lo menos, viene a costa del tiempo.
Aquí es donde es fácil ver cuánto arte hay en la creación de código. Para cualquiera de los anteriores, puedo contarles historias sobre cómo se pensó que demasiado o muy poco era la elección incorrecta. Quién está pensando aquí también es una parte importante del contexto.
Por ejemplo, con respecto a DRY: en un trabajo que tenía, heredé un código base que tenía al menos declaraciones redundantes en un 80 %, porque su autor aparentemente no sabía cómo y cuándo escribir una función. El otro 20% del código era confusamente similar a sí mismo.
Me encargaron agregarle algunas características. Una de esas características tendría que repetirse en todo el código que se implementará, y cualquier código futuro tendría que copiarse cuidadosamente para hacer uso de la nueva característica.
Obviamente, necesitaba ser refactorizado solo por mi propia cordura (alto valor) y para futuros desarrolladores. Pero, como era nuevo en el código base, primero escribí pruebas para poder asegurarme de que mi refactorización no introdujera ninguna regresión. De hecho, hicieron exactamente eso: detecté dos errores en el camino que no habría notado entre todo el galimatías producido por el script.
Al final, pensé que lo había hecho bastante bien. Después de la refactorización, impresioné a mi jefe por haber implementado lo que se había considerado una función difícil con unas pocas líneas de código simples; además, el código fue en general un orden de magnitud más eficaz. Pero no pasó mucho tiempo después de esto que el mismo jefe me dijo que había sido demasiado lento y que el proyecto ya debería haber terminado. Traducción: la eficiencia de la codificación era una prioridad más alta.
Cuidado: optimizar al máximo cualquier [aspecto] en particular tendrá el costo de otros. Por lo menos, viene a costa del tiempo.
Sigo pensando que tomé el rumbo correcto allí, incluso si mi jefe no apreció directamente la optimización del código en ese momento. Sin la refactorización y las pruebas, creo que habría llevado más tiempo corregirlo, es decir, centrarse en la velocidad de codificación realmente lo habría frustrado. (¡Oye, ese es nuestro tema!)
Compare esto con un trabajo que hice en un pequeño proyecto paralelo mío. En el proyecto, estaba probando un nuevo motor de plantillas y quería adquirir buenos hábitos desde el principio, aunque probar el nuevo motor de plantillas no era el objetivo final del proyecto.
Tan pronto como noté que algunos bloques que había agregado eran muy similares entre sí y, además, cada bloque requería referirse a la misma variable tres veces, la campana DRY sonó en mi cabeza y me dispuse a encontrar el correcto. manera de hacer lo que estaba tratando de hacer con este motor de plantilla.
Resultó, después de un par de horas de depuración infructuosa, que esto no era posible actualmente con el motor de plantillas de la forma que imaginaba. No solo no había una solución SECA perfecta ; ¡no había ninguna solución SECA en absoluto!
Tratando de optimizar este valor mío, descarrilé por completo mi eficiencia de codificación y mi felicidad, porque este desvío le costó a mi proyecto el progreso que podría haber tenido ese día.
Incluso entonces, ¿estaba completamente equivocado? A veces, vale la pena invertir un poco, especialmente en un nuevo contexto tecnológico, para conocer las mejores prácticas más temprano que tarde. Menos código para reescribir y malos hábitos para deshacer, ¿verdad?
No, creo que fue imprudente incluso buscar una forma de reducir la repetición en mi código, en marcado contraste con mi actitud en la anécdota anterior. La razón es que el contexto lo es todo: estaba explorando una nueva pieza de tecnología en un pequeño proyecto de juego, no me conformaba con el largo plazo. Unas pocas líneas extra y la repetición no habrían lastimado a nadie, pero la pérdida de enfoque me perjudicó a mí y a mi proyecto.
Espera, ¿entonces buscar las mejores prácticas puede ser un mal hábito? A veces. Si mi objetivo principal fuera aprender el nuevo motor o aprender en general, habría sido un tiempo bien invertido: retocar, encontrar los límites, descubrir características no relacionadas y trampas a través de la investigación. Pero había olvidado que ese no era mi objetivo principal, y me costó.
Es un arte, como dije. Y el desarrollo de ese arte se beneficia del recordatorio, No lo hagas . Al menos te permite considerar qué valores están en juego mientras trabajas y cuáles son los más importantes para ti en tu contexto.
¿Qué pasa con la segunda regla? ¿ Cuándo podemos realmente optimizar?
2. No lo hagas todavía : alguien ya lo ha hecho
De acuerdo, ya sea por usted o por otra persona, descubre que su arquitectura ya se ha configurado, los flujos de datos se han pensado y documentado, y es hora de codificar.
Llevemos No lo hagas aún un paso más allá: Ni siquiera lo codifiques todavía .
Esto en sí mismo puede oler como una optimización prematura, pero es una excepción importante. ¿Por qué? Para evitar el temido NIHS, o el síndrome "No inventado aquí", suponiendo que sus prioridades incluyen el rendimiento del código y la minimización del tiempo de desarrollo. De lo contrario, si sus objetivos están completamente orientados al aprendizaje, puede omitir la siguiente sección.
Si bien es posible que las personas reinventen la rueda cuadrada por pura arrogancia, creo que las personas honestas y humildes, como usted y yo, pueden cometer este error simplemente por no conocer todas las opciones disponibles para nosotros. Conocer cada opción de cada API y herramienta en su pila y mantenerse al tanto de ellas a medida que crecen y evolucionan es sin duda mucho trabajo.
Pero dedicar este tiempo es lo que te convierte en un experto y evita que seas la enésima persona en CodeSOD en ser maldecida y burlada por el rastro de devastación dejado por su fascinante interpretación de las calculadoras de fecha y hora o los manipuladores de cadenas.
(Un buen contrapunto a este patrón general es la antigua API de Calendar de Java, pero ya se ha solucionado).
Verifique su biblioteca estándar, verifique el ecosistema de su marco, busque FOSS que ya resuelva su problema
Lo más probable es que los conceptos con los que está tratando tengan nombres bastante estándar y conocidos, por lo que una búsqueda rápida en Internet le ahorrará mucho tiempo.
Como ejemplo, recientemente me estaba preparando para hacer un análisis de estrategias de IA para un juego de mesa. Me desperté una mañana y me di cuenta de que el análisis que estaba planeando se podía hacer mucho más eficientemente si simplemente usaba cierto concepto de combinatoria que recordaba. Al no estar interesado en descubrir el algoritmo para este concepto yo mismo en este momento, ya estaba adelantado al saber el nombre correcto para buscar. Sin embargo, descubrí que después de unos 50 minutos de investigación y de probar un código preliminar, no había logrado convertir el pseudocódigo a medio terminar que había encontrado en una implementación correcta. (¿Puedes creer que hay una publicación de blog en la que el autor asume un resultado de algoritmo incorrecto, implementa el algoritmo incorrectamente para que coincida con las suposiciones, los comentaristas lo señalan y luego, años después, todavía no está solucionado?) En ese punto, mi té de la mañana se activó y busqué [name of concept] [my programming language] . 30 segundos después, tenía el código probablemente correcto de GitHub y estaba pasando a lo que realmente quería hacer. Ser específico e incluir el idioma, en lugar de asumir que tendría que implementarlo yo mismo, significó todo.
Es hora de diseñar su estructura de datos e implementar su algoritmo
…de nuevo, no juegues al código de golf. Priorizar la corrección y la claridad en los proyectos del mundo real.
De acuerdo, lo ha buscado y no hay nada que ya esté resolviendo su problema integrado en su cadena de herramientas o con licencia liberal en la web. Despliegas el tuyo.
No hay problema. El consejo es simple, en este orden:
- Diséñelo para que sea fácil de explicar a un programador novato.
- Escriba una prueba que se ajuste a las expectativas producidas por ese diseño.
- Escriba su código para que un programador novato pueda extraer fácilmente el diseño de él.
Simple, pero quizás difícil de seguir. Aquí es donde entran en juego los hábitos de codificación y los olores del código, el arte, la artesanía y la elegancia. Obviamente, hay un aspecto de ingeniería en lo que está haciendo en este momento, pero nuevamente, no juegue al golf de código. Priorizar la corrección y la claridad en los proyectos del mundo real.
Si te gustan los videos, aquí tienes uno de alguien que sigue los pasos anteriores, más o menos. Para los reacios a los videos, resumiré: es una prueba de codificación de algoritmos en una entrevista de trabajo de Google. El entrevistado primero diseña el algoritmo de una manera que sea fácil de comunicar. Antes de escribir cualquier código, hay ejemplos de la salida esperada por un diseño de trabajo. Entonces el código sigue naturalmente.
En cuanto a las pruebas en sí, sé que en algunos círculos, el desarrollo basado en pruebas puede ser polémico. Creo que parte de la razón es que puede exagerarse, perseguirse religiosamente hasta el punto de sacrificar el tiempo de desarrollo. (De nuevo, disparándonos en el pie tratando de optimizar incluso una variable demasiado desde el principio). Incluso Kent Beck no lleva TDD a tal extremo, e inventó la programación extrema y escribió el libro sobre TDD. Así que comience con algo simple para asegurarse de que su salida sea correcta. Después de todo, lo harías manualmente después de codificar de todos modos, ¿verdad? (Mis disculpas si eres un programador tan rockstar que ni siquiera ejecutas tu código después de escribirlo por primera vez. En ese caso, tal vez considerarías dejar a los futuros mantenedores de tu código con una prueba solo para que sepas que no lo harán). rompa su increíble implementación). Entonces, en lugar de hacer una diferencia visual manual, con una prueba en su lugar, ya está dejando que la computadora haga ese trabajo por usted.
Durante el proceso bastante mecánico de implementar sus algoritmos y estructuras de datos, evite hacer optimizaciones línea por línea, y ni siquiera piense en usar un lenguaje externo de nivel inferior personalizado (Asamblea si está codificando en C, C si está codificando en Perl, etc.) en este punto. La razón es simple: si su algoritmo se reemplaza por completo, y no sabrá hasta más adelante en el proceso si es necesario, entonces sus esfuerzos de optimización de bajo nivel no tendrán ningún efecto al final.
Un ejemplo de ECMAScript
En el excelente sitio de revisión de código de la comunidad exercism.io, recientemente encontré un ejercicio que sugería explícitamente intentar optimizar para la deduplicación o para mayor claridad. Optimicé la deduplicación, solo para mostrar cuán ridículas pueden ser las cosas si lleva DRY, una mentalidad de codificación beneficiosa, como mencioné anteriormente, demasiado lejos. Así es como se veía mi código:
const zeroPhrase = "No more"; const wallPhrase = " on the wall"; const standardizeNumber = number => { if (number === 0) { return zeroPhrase; } return '' + number; } const bottlePhrase = number => { const possibleS = (number === 1) ? '' : 's'; return standardizeNumber(number) + " bottle" + possibleS + " of beer"; } export default class Beer { static verse(number) { const nextNumber = (number === 0) ? 99 : (number - 1); const thisBottlePhrase = bottlePhrase(number); const nextBottlePhrase = bottlePhrase(nextNumber); let phrase = thisBottlePhrase + wallPhrase + ", " + thisBottlePhrase.toLowerCase() + ".\n"; if (number === 0) { phrase += "Go to the store and buy some more"; } else { const bottleReference = (number === 1) ? "it" : "one"; phrase += "Take " + bottleReference + " down and pass it around"; } return phrase + ", " + nextBottlePhrase.toLowerCase() + wallPhrase + ".\n"; } static sing(start = 99, end = 0) { return Array.from(Array(start - end + 1).keys()).map(offset => { return this.verse(start - offset); }).join('\n'); } } ¡Apenas hay duplicación de cadenas allí! Al escribirlo de esta manera, implementé manualmente una forma de compresión de texto para la canción de la cerveza (pero solo para la canción de la cerveza). ¿Cuál fue el beneficio, exactamente? Bueno, digamos que quieres cantar sobre beber cerveza en latas en lugar de botellas. Podría lograr esto cambiando una sola instancia de bottle a can .
¡Agradable!
…¿Correcto?
No, porque entonces todas las pruebas se rompen. De acuerdo, eso es fácil de arreglar: solo haremos una búsqueda y reemplazaremos por bottle en la especificación de prueba de unidad. Y eso es exactamente tan fácil de hacer como hacerlo con el propio código en primer lugar y conlleva los mismos riesgos de romper cosas sin querer.
Mientras tanto, mis variables tendrán un nombre extraño después, con cosas como bottlePhrase que no tienen nada que ver con las botellas en absoluto. La única forma de evitar esto es haber previsto exactamente el tipo de cambio que se haría y haber usado un término más genérico como vessel o container en lugar de bottle en mis nombres de variables.
La sabiduría de la prueba de futuro de esta manera es bastante cuestionable. ¿Cuáles son las probabilidades de que quieras cambiar algo? Y si lo hace, ¿lo que cambie funcionará tan convenientemente? En el ejemplo de bottlePhrase , ¿qué sucede si desea localizar en un idioma que tiene más de dos formas plurales? Así es, refactorice el tiempo y el código puede verse aún peor después.
Pero cuando sus requisitos cambian, y no solo está tratando de anticiparlos, entonces tal vez sea hora de refactorizar. O tal vez aún pueda posponerlo: ¿cuántos tipos de embarcaciones o localizaciones agregará, de manera realista? De todos modos, cuando necesite equilibrar su deduplicación con claridad, vale la pena ver esta demostración de Katrina Owen.
Volviendo a mi feo ejemplo: no hace falta decir que los beneficios de la deduplicación ni siquiera se están dando cuenta aquí. Mientras tanto, ¿cuánto costó?
Aparte de tomar más tiempo para escribir en primer lugar, ahora es un poco menos trivial de leer, depurar y mantener. Imagine el nivel de legibilidad con una cantidad moderada de duplicación permitida. Por ejemplo, tener cada una de las cuatro variaciones de verso explicadas.
¡Pero todavía no hemos optimizado!
Ahora que su algoritmo está implementado y ha demostrado que su salida es correcta, ¡felicitaciones! ¡Tienes una línea de base!
Finalmente, es hora de... optimizar, ¿verdad? No, todavía No lo hagas todavía . Es hora de tomar su línea de base y hacer un buen punto de referencia . Establezca un umbral para sus expectativas en torno a esto y manténgalo en su conjunto de pruebas. Luego, si algo hace que este código sea más lento de repente, incluso si todavía funciona, lo sabrás antes de que salga por la puerta.
Todavía posponga la optimización, hasta que tenga implementada una parte completa de la experiencia de usuario relevante. Hasta ese momento, es posible que esté apuntando a una parte del código completamente diferente de la que necesita.
Vaya a terminar su aplicación (o componente), si aún no lo ha hecho, configurando todas sus líneas de base algorítmicas de referencia a medida que avanza.
Una vez hecho esto, este es un buen momento para crear y comparar pruebas de extremo a extremo que cubran los escenarios de uso del mundo real más comunes de su sistema.
Tal vez encuentres que todo está bien.
O tal vez haya determinado que, en su contexto de la vida real, algo es demasiado lento o requiere demasiada memoria.
Bien, ahora puedes optimizar
Sólo hay una manera de ser objetivo al respecto. Es hora de sacar gráficos de llamas y otras herramientas de creación de perfiles. Los ingenieros experimentados pueden o no adivinar mejor con más frecuencia que los novatos, pero ese no es el punto: la única forma de saberlo con seguridad es perfilar. Esto siempre es lo primero que se debe hacer en el proceso de optimización del código para el rendimiento.
Puede perfilar durante una prueba de extremo a extremo dada para llegar a lo que realmente tendrá el mayor impacto. (Y luego, después de la implementación, monitorear los patrones de uso es una excelente manera de estar al tanto de qué aspectos de su sistema son los más relevantes para medir en el futuro).
Tenga en cuenta que no está tratando de usar el generador de perfiles en toda su profundidad: está buscando más perfiles de nivel de función que perfiles de nivel de declaración, en general, porque su objetivo en este punto es solo averiguar qué algoritmo es el cuello de botella .
Ahora que ha utilizado la creación de perfiles para identificar el cuello de botella de su sistema, ahora puede intentar optimizarlo, seguro de que vale la pena hacerlo. También puede probar qué tan efectivo (o ineficaz) fue su intento, gracias a los puntos de referencia de referencia que hizo en el camino.
Técnicas Generales
Primero, recuerda mantenerte en un nivel alto el mayor tiempo posible:
¿Sabías? El último truco de optimización universal, se aplica en todos los casos:
— Lars Doucet (@larsiusprime) 30 de marzo de 2017
- Dibujar menos cosas
- Actualizar menos cosas
A nivel de algoritmo completo, una técnica es la reducción de la fuerza. Sin embargo, en el caso de reducir los bucles a fórmulas, tenga cuidado de dejar comentarios. No todos conocen o recuerdan todas las fórmulas de combinatoria. Además, tenga cuidado con el uso de las matemáticas: a veces, lo que cree que podría ser una reducción de la fuerza, al final no lo es. Por ejemplo, supongamos que x * (y + z) tiene un significado algorítmico claro. Si su cerebro ha sido entrenado en algún momento, por la razón que sea, para desagrupar automáticamente términos similares, es posible que tenga la tentación de reescribirlo como x * y + x * z . Por un lado, esto pone una barrera entre el lector y el claro significado algorítmico que había estado allí. (Peor aún, ahora en realidad es menos eficiente debido a la operación de multiplicación adicional requerida. Es como si un bucle se desenrollara simplemente apelmazado). En cualquier caso, una nota rápida sobre sus intenciones sería de gran ayuda e incluso podría ayudarlo a ver su propio error antes de cometerlo.
Ya sea que esté usando una fórmula o simplemente reemplazando un algoritmo basado en bucles con otro algoritmo basado en bucles, está listo para medir la diferencia.
Pero tal vez pueda obtener un mejor rendimiento simplemente cambiando su estructura de datos. Infórmese sobre la diferencia de rendimiento entre las diversas operaciones que debe realizar en la estructura que está utilizando y en cualquier alternativa. Tal vez un hash parezca un poco más complicado para trabajar dentro de su contexto, pero ¿vale la pena el tiempo de búsqueda superior en una matriz? Estos son los tipos de compensaciones sobre las que usted debe decidir.
Puede notar que esto se reduce a saber qué algoritmos se ejecutan en su nombre cuando llama a una función de conveniencia. Entonces, al final, es lo mismo que la reducción de fuerza. Y saber qué están haciendo las bibliotecas de su proveedor detrás de escena es crucial no solo para el rendimiento sino también para evitar errores no intencionales.
Micro-Optimizaciones
Bien, la funcionalidad de su sistema está lista, pero desde el punto de vista de UX, el rendimiento podría ajustarse un poco más. Suponiendo que haya hecho todo lo posible más arriba, es hora de considerar las optimizaciones que hemos estado evitando todo el tiempo hasta ahora. Considere, porque este nivel de optimización sigue siendo una compensación contra la claridad y la mantenibilidad. Pero ha decidido que es el momento, así que siga adelante con la creación de perfiles a nivel de declaración, ahora que está dentro del contexto de todo el sistema, donde realmente importa.
Al igual que con las bibliotecas que utiliza, se han invertido innumerables horas de ingeniería para su beneficio a nivel de su compilador o intérprete. (Después de todo, la optimización del compilador y la generación de código son temas muy importantes). Esto es cierto incluso a nivel de procesador. Intentar optimizar el código sin ser consciente de lo que sucede en los niveles más bajos es como pensar que tener tracción en las cuatro ruedas implica que tu vehículo también puede detenerse más fácilmente.
Es difícil dar un buen consejo genérico más allá de eso porque realmente depende de su pila tecnológica y de lo que apunta su generador de perfiles. Pero, debido a que está midiendo, ya está en una excelente posición para pedir ayuda, si las soluciones no se le presentan de manera orgánica e intuitiva desde el contexto del problema. (El sueño y el tiempo dedicado a pensar en otra cosa también pueden ayudar).
En este punto, según el contexto y los requisitos de escalado, Jeff Atwood probablemente sugeriría simplemente agregar hardware, lo que puede ser más económico que el tiempo del desarrollador.
Tal vez no vayas por ese camino. En ese caso, puede ser útil explorar varias categorías de técnicas de optimización de código:
- almacenamiento en caché
- Bit hacks y los específicos de entornos de 64 bits
- Optimización de bucle
- Optimización de la jerarquía de memoria
Más específicamente:
- Consejos de optimización de código en C y C++
- Consejos de optimización de código en Java
- Optimización del uso de la CPU en .NET
- Almacenamiento en caché de la granja web ASP.NET
- Ajuste de base de datos SQL o ajuste de Microsoft SQL Server en particular
- Escalando el juego de Scala! estructura
- Optimización avanzada del rendimiento de WordPress
- Optimización de código con prototipo de JavaScript y cadenas de alcance
- Optimización del rendimiento de React
- Eficiencia de animación de iOS
- Consejos de rendimiento de Android
En cualquier caso, tengo algunas prohibiciones más para ti:
No reutilice una variable para múltiples propósitos distintos. En términos de mantenibilidad, esto es como hacer funcionar un automóvil sin aceite. Solo en las situaciones incrustadas más extremas esto tuvo sentido, e incluso en esos casos, diría que ya no lo tiene. Este es el trabajo del compilador para organizar. Hágalo usted mismo, luego mueva una línea de código y habrá introducido un error. ¿Te vale la ilusión de salvar la memoria?
No utilice macros y funciones en línea sin saber por qué. Sí, la sobrecarga de la llamada de función es un costo. Pero evitarlo a menudo hace que su código sea más difícil de depurar y, a veces, lo hace más lento. Usar esta técnica en todas partes solo porque es una buena idea de vez en cuando es un ejemplo de un martillo dorado.
No desenrolle los bucles a mano. Una vez más, esta forma de optimización de bucles es algo que casi siempre se optimiza mejor con un proceso automatizado como la compilación, sin sacrificar la legibilidad de su código.
La ironía de los últimos dos ejemplos de optimización de código es que en realidad pueden ser anti-rendimiento. Por supuesto, dado que está haciendo pruebas comparativas, puede probar o refutar eso para su código en particular. Pero incluso si ve una mejora en el rendimiento, regrese al lado artístico y vea si la ganancia vale la pena la pérdida en legibilidad y mantenibilidad.
Es tuyo: Optimización óptimamente optimizada
Intentar optimizar el rendimiento puede ser beneficioso. Sin embargo, la mayoría de las veces, se hace muy prematuramente, conlleva una letanía de efectos secundarios negativos y, lo que es más irónico, conduce a un peor rendimiento. Espero que se haya ido con una mayor apreciación del arte y la ciencia de la optimización y, lo que es más importante, su contexto adecuado.
Estoy feliz si esto nos ayuda a desechar la noción de escribir un código perfecto desde el principio y escribir el código correcto en su lugar. Debemos recordar optimizar de arriba hacia abajo, probar dónde se encuentran los cuellos de botella y medir antes y después de solucionarlos. Esa es la estrategia óptima, óptima para optimizar la optimización. La mejor de las suertes.

