Articulo de referencia

Eficiencia algorítmica

En informática , la eficiencia algorítmica es una propiedad de un algoritmo que se relaciona con la cantidad de recursos computacionales que utiliza. La eficiencia algorítmica p...

En informática , la eficiencia algorítmica es una propiedad de un algoritmo que se relaciona con la cantidad de recursos computacionales que utiliza. La eficiencia algorítmica puede considerarse análoga a la productividad en ingeniería para un proceso repetitivo o continuo.

Para lograr la máxima eficiencia, es deseable minimizar el uso de recursos. Sin embargo, no se pueden comparar directamente recursos distintos, como la complejidad temporal y espacial , por lo que la eficiencia de dos algoritmos suele depender de qué medida de eficiencia se considere más importante.

Por ejemplo, Cycle Sort y Timsort son ambos algoritmos para ordenar una lista de elementos de menor a mayor. Cycle Sort organiza la lista en tiempo proporcional al cuadrado del número de elementos (O(norte2){\textstyle O(n^{2})}, ver notación de O grande ), pero minimiza las escrituras en el arreglo original y solo requiere una pequeña cantidad de memoria adicional que es constante con respecto a la longitud de la lista (O(1){\textstyle O(1)}). Timsort ordena la lista en tiempo linealítmico (proporcional a una cantidad multiplicada por su logaritmo) en la longitud de la lista (O(norteregistronorte){\textstyle O(n\log n)}), pero tiene un requerimiento de espacio lineal en la longitud de la lista (O(norte){\textstyle O(n)}Si se deben ordenar listas grandes a alta velocidad para una aplicación determinada, timsort es una mejor opción; sin embargo, si es más importante minimizar los ciclos de programación/borrado  y el consumo de memoria de la ordenación, cycle sort es una mejor opción.

Fondo

Ada Lovelace destacó en 1843 la importancia de la eficiencia en relación con el tiempo, aplicándola a la máquina analítica mecánica de Charles Babbage :

"En casi cualquier cálculo es posible una gran variedad de disposiciones para la sucesión de los procesos, y diversas consideraciones deben influir en la selección entre ellas para los fines de una máquina de cálculo. Un objetivo esencial es elegir aquella disposición que tienda a reducir al mínimo el tiempo necesario para completar el cálculo" [ 1 ].

Las primeras computadoras electrónicas tenían una velocidad y una memoria de acceso aleatorio limitadas . Por lo tanto, se producía una disyuntiva entre espacio y tiempo . Una tarea podía utilizar un algoritmo rápido que consumiera mucha memoria, o un algoritmo lento que consumiera poca memoria. La disyuntiva de ingeniería consistía, por lo tanto, en utilizar el algoritmo más rápido que pudiera caber en la memoria disponible.

Las computadoras modernas son significativamente más rápidas que las primeras y tienen una cantidad de memoria mucho mayor disponible ( gigabytes en lugar de kilobytes ). Sin embargo, Donald Knuth enfatizó que la eficiencia sigue siendo una consideración importante:

"En las disciplinas de ingeniería establecidas, una mejora del 12%, fácilmente obtenible, nunca se considera marginal y creo que el mismo punto de vista debería prevalecer en la ingeniería de software" [ 2 ].

En la era de la IA , si bien los LLM pueden generar código que funciona, estos a menudo no cumplen con los estándares de rendimiento requeridos en aplicaciones con recursos limitados o sensibles al tiempo [ 3 ] , lo que convierte la eficiencia del código en un cuello de botella crítico para la implementación en el mundo real.

Descripción general

Un algoritmo se considera eficiente si su consumo de recursos, también conocido como coste computacional, se encuentra en un nivel aceptable o por debajo de él. En términos generales, «aceptable» significa que se ejecutará en un tiempo o espacio razonable en un ordenador disponible, generalmente en función del tamaño de la entrada. Desde la década de 1950, los ordenadores han experimentado aumentos drásticos tanto en la potencia de cálculo como en la cantidad de memoria disponible, por lo que los niveles aceptables actuales habrían sido inaceptables incluso hace 10 años. De hecho, gracias a que la potencia de cálculo se duplica aproximadamente cada 2 años , las tareas que son aceptablemente eficientes en los smartphones y sistemas embebidos modernos podrían haber sido inaceptablemente ineficientes para los servidores industriales hace 10 años.

Los fabricantes de ordenadores suelen lanzar nuevos modelos, a menudo con un rendimiento superior . El coste del software puede ser bastante elevado, por lo que, en algunos casos, la forma más sencilla y económica de obtener un mayor rendimiento podría ser simplemente comprar un ordenador más rápido, siempre que sea compatible con el ordenador que ya se tiene.

Existen muchas maneras de medir los recursos que utiliza un algoritmo: las dos medidas más comunes son la velocidad y el uso de memoria; otras medidas podrían incluir la velocidad de transmisión, el uso temporal y a largo plazo del disco, el consumo de energía, el costo total de propiedad , el tiempo de respuesta a estímulos externos, etc. Muchas de estas medidas dependen del tamaño de la entrada del algoritmo, es decir, la cantidad de datos que se deben procesar. También pueden depender de la forma en que están organizados los datos; por ejemplo, algunos algoritmos de ordenación tienen un rendimiento deficiente con datos que ya están ordenados o que están ordenados en orden inverso.

En la práctica, existen otros factores que pueden afectar la eficiencia de un algoritmo, como los requisitos de precisión y/o fiabilidad. Como se detalla a continuación, la forma en que se implementa un algoritmo también puede tener un efecto significativo en su eficiencia real, si bien muchos aspectos de esto están relacionados con cuestiones de optimización .

Análisis teórico

En el análisis teórico de algoritmos , la práctica habitual es estimar su complejidad en el sentido asintótico. La notación más utilizada para describir el consumo de recursos o "complejidad" es la notación Big O de Donald Knuth , que representa la complejidad de un algoritmo en función del tamaño de la entrada.norte{\textstyle n}La notación Big O es una medida asintótica de la complejidad de la función, dondeF(norte)=O(gramo(norte)){\textstyle f(n)=O{\bigl (}g(n){\bigr )}}aproximadamente significa que el tiempo requerido para un algoritmo es proporcional agramo(norte){\displaystyle g(n)}, omitiendo términos de orden inferior que contribuyen menos quegramo(norte){\displaystyle g(n)}al crecimiento de la función comonorte{\textstyle n}crece arbitrariamente grande . Esta estimación puede ser engañosa cuandonorte{\textstyle n}es pequeño, pero generalmente es suficientemente preciso cuandonorte{\textstyle n}es grande ya que la notación es asintótica. Por ejemplo, el ordenamiento de burbuja puede ser más rápido que el ordenamiento por fusión cuando solo se deben ordenar unos pocos elementos; sin embargo, es probable que cualquiera de las dos implementaciones cumpla con los requisitos de rendimiento para una lista pequeña. Por lo general, los programadores están interesados ​​en algoritmos que escalen eficientemente a grandes tamaños de entrada, y el ordenamiento por fusión se prefiere al ordenamiento de burbuja para listas de longitud que se encuentran en la mayoría de los programas intensivos en datos.

Algunos ejemplos de la notación Big O aplicada a la complejidad temporal asintótica de los algoritmos incluyen:

Medición del rendimiento

Para las nuevas versiones de software o para realizar comparaciones con sistemas de la competencia, a veces se utilizan pruebas de rendimiento (benchmarks) , que ayudan a evaluar el desempeño relativo de un algoritmo. Si se desarrolla un nuevo algoritmo de ordenación , por ejemplo, se puede comparar con sus predecesores para asegurar que, al menos, mantenga la misma eficiencia con datos conocidos, teniendo en cuenta cualquier mejora funcional. Los clientes pueden usar las pruebas de rendimiento al comparar diversos productos de proveedores alternativos para estimar qué producto se adapta mejor a sus requisitos específicos en términos de funcionalidad y rendimiento. Por ejemplo, en el mundo de los mainframes , ciertos productos de ordenación propietarios de empresas de software independientes, como Syncsort, compiten en velocidad con productos de los principales proveedores, como IBM .

Algunos benchmarks ofrecen oportunidades para producir un análisis que compare la velocidad relativa de varios lenguajes compilados e interpretados, por ejemplo [ 4 ] [ 5 ] y The Computer Language Benchmarks Game compara el rendimiento de implementaciones de problemas de programación típicos en varios lenguajes de programación.

Incluso la creación de pruebas comparativas " hágalo usted mismo " puede demostrar el rendimiento relativo de diferentes lenguajes de programación, utilizando diversos criterios especificados por el usuario. Esto es bastante sencillo, como lo demuestra con un ejemplo el "Análisis comparativo del rendimiento de nueve lenguajes" de Christopher W. Cowell-Shah. [ 6 ]

Preocupaciones sobre la implementación

Los problemas de implementación también pueden afectar la eficiencia, como la elección del lenguaje de programación, la forma en que se codifica el algoritmo, [ 7 ] la elección de un compilador para un lenguaje en particular, las opciones de compilación utilizadas o incluso el sistema operativo empleado. En muchos casos, un lenguaje implementado por un intérprete puede ser mucho más lento que un lenguaje implementado por un compilador. [ 4 ] Véanse los artículos sobre compilación justo a tiempo y lenguajes interpretados .

Existen otros factores que pueden afectar los problemas de tiempo o espacio, pero que pueden estar fuera del control del programador; estos incluyen la alineación de datos , la granularidad de los datos , la localidad de la caché , la coherencia de la caché , la recolección de basura , el paralelismo a nivel de instrucción , el multihilo (ya sea a nivel de hardware o software), la multitarea simultánea y las llamadas a subrutinas . [ 8 ]

Algunos procesadores tienen capacidad para el procesamiento vectorial , lo que permite que una sola instrucción opere sobre múltiples operandos ; para un programador o compilador, usar estas capacidades puede ser más o menos sencillo. Los algoritmos diseñados para el procesamiento secuencial podrían necesitar ser rediseñados por completo para aprovechar el procesamiento paralelo , o podrían ser fácilmente reconfigurados. A medida que la computación paralela y distribuida cobró mayor importancia a finales de la década de 2010, se están realizando más inversiones en API de alto nivel eficientes para sistemas de computación paralela y distribuida como CUDA , TensorFlow , Hadoop , OpenMP y MPI .

Otro problema que puede surgir en la programación es que los procesadores compatibles con el mismo conjunto de instrucciones (como x86-64 o ARM ) pueden implementar una instrucción de diferentes maneras, de modo que las instrucciones que son relativamente rápidas en algunos modelos pueden ser relativamente lentas en otros. Esto suele plantear desafíos a los compiladores optimizadores , que deben tener un conocimiento exhaustivo de la CPU específica y demás hardware disponible en el destino de compilación para optimizar al máximo el rendimiento de un programa. En casos extremos, un compilador puede verse obligado a emular instrucciones no compatibles con la plataforma de destino de compilación, lo que le obliga a generar código o enlazar una llamada a una biblioteca externa para producir un resultado que, de otro modo, sería incalculable en esa plataforma, incluso si está soportado de forma nativa y es más eficiente en hardware en otras plataformas. Este suele ser el caso en sistemas embebidos con respecto a la aritmética de punto flotante , donde los microcontroladores pequeños y de bajo consumo a menudo carecen de soporte de hardware para la aritmética de punto flotante y, por lo tanto, requieren rutinas de software computacionalmente costosas para realizar cálculos de punto flotante.

Medidas de uso de recursos

Las medidas normalmente se expresan como una función del tamaño de la entrada.norte{\displaystyle \scriptstyle {n}}.

Las dos medidas más comunes son:

  • Tiempo : ¿Cuánto tarda el algoritmo en completarse?
  • Espacio : ¿Cuánta memoria de trabajo (normalmente RAM) necesita el algoritmo? Esto tiene dos aspectos: la cantidad de memoria que necesita el código (uso de espacio auxiliar) y la cantidad de memoria que necesita para los datos con los que opera el código (uso de espacio intrínseco).

Para ordenadores cuya alimentación se suministra mediante batería (por ejemplo, portátiles y teléfonos inteligentes ), o para cálculos muy largos/grandes (por ejemplo, superordenadores ), otras medidas de interés son:

  • Consumo directo de energía : energía necesaria directamente para el funcionamiento del ordenador.
  • Consumo indirecto de energía : energía necesaria para refrigeración, iluminación, etc.

A partir de 2018El consumo de energía se está convirtiendo en una métrica importante para tareas computacionales de todo tipo y a todas las escalas, desde dispositivos integrados de Internet de las cosas hasta sistemas en chip y granjas de servidores . Esta tendencia se conoce a menudo como computación verde .

En algunos casos, también pueden ser relevantes medidas menos comunes de eficiencia computacional:

  • Tamaño de transmisión : el ancho de banda puede ser un factor limitante. La compresión de datos permite reducir la cantidad de datos a transmitir. Mostrar una imagen (por ejemplo, el logotipo de Google ) puede implicar la transmisión de decenas de miles de bytes (48 KB en este caso), en comparación con la transmisión de seis bytes para el texto "Google". Esto es importante para tareas de computación con uso intensivo de E/S .
  • Espacio externo : espacio necesario en un disco u otro dispositivo de memoria externa; este podría ser para almacenamiento temporal mientras se ejecuta el algoritmo, o podría ser almacenamiento a largo plazo necesario para futuras consultas.
  • Tiempo de respuesta ( latencia ): esto es particularmente relevante en una aplicación en tiempo real cuando el sistema informático debe responder rápidamente a algún evento externo .
  • Coste total de propiedad : especialmente si un ordenador está dedicado a un algoritmo concreto.

Tiempo

Teoría

El análisis de algoritmos , generalmente mediante conceptos como la complejidad temporal , permite estimar el tiempo de ejecución en función del tamaño de los datos de entrada. El resultado se suele expresar mediante la notación Big O. Esto resulta útil para comparar algoritmos, sobre todo cuando se procesa una gran cantidad de datos. Se necesitan estimaciones más detalladas para comparar el rendimiento de los algoritmos cuando la cantidad de datos es pequeña, aunque esto probablemente sea menos relevante. Los algoritmos paralelos pueden ser más difíciles de analizar .

Práctica

Se puede utilizar un benchmark para evaluar el rendimiento de un algoritmo en la práctica. Muchos lenguajes de programación disponen de una función que proporciona el tiempo de CPU utilizado. Para algoritmos de larga duración, el tiempo transcurrido también puede resultar de interés. Generalmente, los resultados deben promediarse a partir de varias pruebas.

La elaboración de perfiles basada en la ejecución puede ser muy sensible a la configuración del hardware y a la posibilidad de que otros programas o tareas se ejecuten al mismo tiempo en un entorno de multiprocesamiento y multiprogramación .

Este tipo de prueba también depende en gran medida de la selección de un lenguaje de programación, un compilador y unas opciones de compilación concretas, por lo que todos los algoritmos que se comparan deben implementarse bajo las mismas condiciones.

Espacio

Esta sección se centra en el uso de los recursos de memoria ( registros , caché , RAM , memoria virtual , memoria secundaria ) durante la ejecución del algoritmo. Al igual que en el análisis temporal anterior, se analiza el algoritmo, generalmente mediante un análisis de complejidad espacial, para obtener una estimación de la memoria necesaria en tiempo de ejecución en función del tamaño de los datos de entrada. El resultado se suele expresar mediante la notación Big O.

Hay hasta cuatro aspectos del uso de la memoria a tener en cuenta:

Las primeras computadoras electrónicas y las primeras computadoras domésticas tenían cantidades relativamente pequeñas de memoria de trabajo. Por ejemplo, la Calculadora Automática de Almacenamiento con Retardo Electrónico (EDSAC) de 1949 tenía una memoria de trabajo máxima de 1024 palabras de 17 bits, mientras que la Sinclair ZX80 de 1980 venía inicialmente con 1024 bytes de memoria de trabajo de 8 bits. A finales de la década de 2010, es común que las computadoras personales tengan entre 4 y 32 GB de RAM, un aumento de más de 300 millones de veces en la cantidad de memoria.

Jerarquía de almacenamiento en caché y memoria

Los ordenadores modernos pueden tener cantidades de memoria relativamente grandes (posiblemente gigabytes), por lo que tener que comprimir un algoritmo en una cantidad limitada de memoria ya no es el tipo de problema que solía ser. Sin embargo, los diferentes tipos de memoria y sus velocidades de acceso relativas pueden ser importantes:

Un algoritmo cuyas necesidades de memoria se ajusten a la memoria caché será mucho más rápido que uno que se ajuste a la memoria principal, el cual, a su vez, será mucho más rápido que uno que deba recurrir a la paginación. Por ello, las políticas de reemplazo de caché son cruciales para la computación de alto rendimiento, al igual que la programación con optimización de caché y la alineación de datos . Para complicar aún más la situación, algunos sistemas cuentan con hasta tres niveles de memoria caché, con velocidades efectivas variables. Dado que los distintos sistemas disponen de diferentes cantidades de estos tipos de memoria, el efecto de las necesidades de memoria de los algoritmos puede variar considerablemente de un sistema a otro.

En los inicios de la informática, si un algoritmo y sus datos no cabían en la memoria principal, no se podía utilizar. Hoy en día, el uso de memoria virtual parece proporcionar mucha más capacidad, pero a costa del rendimiento. Se puede obtener una velocidad mucho mayor si un algoritmo y sus datos caben en la memoria caché; en este caso, minimizar el espacio también ayuda a minimizar el tiempo. Esto se conoce como el principio de localidad y se puede subdividir en localidad de referencia , localidad espacial y localidad temporal . Un algoritmo que no quepa completamente en la memoria caché, pero que presente localidad de referencia, puede tener un rendimiento razonable.

Véase también

Referencias

  1. Green, Christopher, Clásicos en la historia de la psicología , consultado el 19 de mayo de 2013.
  2. Knuth, Donald (1974), "Programación estructurada con instrucciones goto" (PDF) , Computing Surveys , 6 (4): 261–301 , CiteSeerX 10.1.1.103.6084 , doi : 10.1145/356635.356640 , S2CID 207630080 , archivado del original (PDF) el 24 de agosto de 2009 , recuperado el 19 de mayo de 2013  
  3. ^ Du, Mingzhe; Tuan, Luu Anh; Liu, Yue; Qing, Yuhao; Huang, Dong; Él, Xinyi; Liu, Qian; Mamá, Zejun; Ng, See-kiong (3 de junio de 2025), Afterburner: el aprendizaje por refuerzo facilita la optimización de la eficiencia del código de mejora automática , arXiv : 2505.23387
  4. 1 2 "Prueba comparativa de punto flotante: comparación de lenguajes (Fourmilog: nadie se atreve a llamarlo razón)" . Fourmilab.ch. 4 de agosto de 2005. Recuperado el 14 de diciembre de 2011 .
  5. "Historial de referencia de Whetstone" . Roylongbottom.org.uk . Consultado el 14 de diciembre de 2011 .
  6. Equipo de OSNews. "Resumen del rendimiento de nueve lenguajes: evaluación comparativa de operaciones matemáticas y de entrada/salida de archivos" . osnews.com . Consultado el 18 de septiembre de 2018 .
  7. Kriegel, Hans-Peter ; Schubert, Erich; Zimek, Arthur (2016). "El (oscuro) arte de la evaluación en tiempo de ejecución: ¿Estamos comparando algoritmos o implementaciones?". Knowledge and Information Systems . 52 (2): 341–378 . doi : 10.1007/s10115-016-1004-2 . ISSN 0219-1377 . S2CID 40772241 .  
  8. Guy Lewis Steele, Jr. «Desmintiendo el mito de la "llamada a procedimiento costosa", o, Implementaciones de llamadas a procedimiento consideradas perjudiciales, o, Lambda: El GOTO definitivo». Laboratorio de IA del MIT. Memorando del Laboratorio de IA AIM-443. Octubre de 1977.
  9. 1 2 3 4 Hennessy, John L; Patterson, David A; Asanović, Krste ; Bakos, Jason D; Colwell, Robert P; Bhattacharjee, Abhishek; Conte, Thomas M; Duato, José; Franklin, Diana; Goldberg, David; Jouppi, Norman P ; Li, Sheng; Muralimanohar, Naveen; Peterson, Gregory D; Pinkston, Timothy Mark; Ranganathan, Prakash; Wood, David Allen; Young, Clifford; Zaky, Amr (2011). Arquitectura de computadoras: un enfoque cuantitativo (Sexta ed.). Elsevier Science. ISBN  978-0128119051OCLC 983459758 
  10. ^ Du, Mingzhe; Luu, Anh Tuan; Ji, Bin; Liu, Qian; Ng, See-Kiong (11 de junio de 2024), Mercury: un punto de referencia de eficiencia de código para modelos de lenguaje grande de código , arXiv : 2402.07844
  11. ^ Qing, Yuhao; Zhu, Boyu; Du, Mingzhe; Guo, Zhijiang; Zhuo, Terry Yue; Zhang, Qianru; Zhang, Jie M.; Cui, Heming; Yiu, Siu-Ming (19 de mayo de 2025), EffiBench-X: un punto de referencia multilingüe para medir la eficiencia del código generado por LLM , arXiv : 2505.13004
  12. ^ Huang, Dong; Qing, Yuhao; Shang, Weiyi; Cui, Heming; Zhang, Jie M. (10 de mayo de 2025), EffiBench: evaluación comparativa de la eficiencia del código generado automáticamente , arXiv : 2402.02037