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:

  • Transmission size: bandwidth could be a limiting factor. Data compression can be used to reduce the amount of data to be transmitted. Displaying a picture or image (e.g. Google logo) can result in transmitting tens of thousands of bytes (48K in this case) compared with transmitting six bytes for the text "Google". This is important for I/O bound computing tasks.
  • External space: space needed on a disk or other external memory device; this could be for temporary storage while the algorithm is being carried out, or it could be long-term storage needed to be carried forward for future reference.
  • Response time (latency): this is particularly relevant in a real-time application when the computer system must respond quickly to some external event.
  • Total cost of ownership: particularly if a computer is dedicated to one particular algorithm.

Time

Theory

Analysis of algorithms, typically using concepts like time complexity, can be used to get an estimate of the running time as a function of the size of the input data. The result is normally expressed using Big O notation. This is useful for comparing algorithms, especially when a large amount of data is to be processed. More detailed estimates are needed to compare algorithm performance when the amount of data is small, although this is likely to be of less importance. Parallel algorithms may be more difficult to analyze.

Practice

A benchmark can be used to assess the performance of an algorithm in practice. Many programming languages have an available function which provides CPU time usage. For long-running algorithms the elapsed time could also be of interest. Results should generally be averaged over several tests.

Run-based profiling can be very sensitive to hardware configuration and the possibility of other programs or tasks running at the same time in a multi-processing and multi-programming environment.

This sort of test also depends heavily on the selection of a particular programming language, compiler, and compiler options, so algorithms being compared must all be implemented under the same conditions.

Space

This section is concerned with use of memory resources (registers, cache, RAM, virtual memory, secondary memory) while the algorithm is being executed. As for time analysis above, analyze the algorithm, typically using space complexity analysis to get an estimate of the run-time memory needed as a function as the size of the input data. The result is normally expressed using Big O notation.

There are up to four aspects of memory usage to consider:

  • The amount of memory needed to hold the code for the algorithm.
  • The amount of memory needed for the input data.
  • The amount of memory needed for any output data.
    • Some algorithms, such as sorting, often rearrange the input data and do not need any additional space for output data. This property is referred to as "in-place" operation.
  • The amount of memory needed as working space during the calculation.

Early electronic computers, and early home computers, had relatively small amounts of working memory. For example, the 1949 Electronic Delay Storage Automatic Calculator (EDSAC) had a maximum working memory of 1024 17-bit words, while the 1980 Sinclair ZX80 came initially with 1024 8-bit bytes of working memory. In the late 2010s, it is typical for personal computers to have between 4 and 32 GB of RAM, an increase of over 300 million times as much memory.

Caching and memory hierarchy

Modern computers can have relatively large amounts of memory (possibly gigabytes), so having to squeeze an algorithm into a confined amount of memory is not the kind of problem it used to be. However, the different types of memory and their relative access speeds can be significant:

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