Eficiencia algorítmica

Property of an algorithm

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 de ingeniería para un proceso repetitivo o continuo.

Para lograr la máxima eficiencia, es conveniente minimizar el uso de recursos. Sin embargo, no es posible comparar directamente distintos recursos, como la complejidad temporal y espacial , por lo que cuál de los dos algoritmos se considera más eficiente a menudo depende de qué medida de eficiencia se considera más importante.

Por ejemplo, tanto el método de ordenación de burbuja como el de ordenación temporal son algoritmos para ordenar una lista de elementos del más pequeño al más grande. El método de ordenación de burbuja organiza la lista en un tiempo proporcional al número de elementos al cuadrado ( , consulte la notación Big O ), pero solo requiere una pequeña cantidad de memoria adicional que es constante con respecto a la longitud de la lista ( ). El método de ordenación temporal ordena la lista en un tiempo lineal (proporcional a una cantidad multiplicada por su logaritmo) en la longitud de la lista ( ), pero tiene un requisito de espacio lineal en la longitud de la lista ( ). Si se deben ordenar listas grandes a alta velocidad para una aplicación determinada, el método de ordenación temporal es una mejor opción; sin embargo, si es más importante minimizar la huella de memoria de la ordenación, el método de ordenación de burbuja es una mejor opción. O ( n 2 ) {\textstyle O(n^{2})} O ( 1 ) {\textstyle O(1)} O ( n log n ) {\textstyle O(n\log n)} O ( n ) {\textstyle O(n)}

Fondo

La importancia de la eficiencia con respecto al tiempo fue enfatizada por Ada Lovelace en 1843 al aplicarla a la máquina analítica mecánica de Charles Babbage :

"En casi todos los cálculos es posible una gran variedad de disposiciones para la sucesión de los procesos, y diversas consideraciones deben influir en la selección de una de 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 un equilibrio entre el espacio y el tiempo . Una tarea podía utilizar un algoritmo rápido que utilizara mucha memoria, o podía utilizar un algoritmo lento que utilizara poca memoria. Por lo tanto, el equilibrio de ingeniería consistía en utilizar el algoritmo más rápido que pudiera caber en la memoria disponible.

Los ordenadores modernos son mucho más rápidos que los antiguos y tienen una cantidad de memoria disponible mucho mayor ( gigabytes en lugar de kilobytes ). Sin embargo, Donald Knuth destacó que la eficiencia sigue siendo un factor importante:

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

Descripción general

Un algoritmo se considera eficiente si su consumo de recursos, también conocido como costo computacional, es igual o inferior a un nivel aceptable. En términos generales, "aceptable" significa: se ejecutará en una cantidad razonable de tiempo o espacio en una computadora disponible, generalmente en función del tamaño de la entrada. Desde la década de 1950, las computadoras han experimentado aumentos dramáticos tanto en la potencia computacional disponible 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 la duplicación aproximada de la potencia de las computadoras cada 2 años , las tareas que son aceptablemente eficientes en los teléfonos inteligentes modernos y los sistemas integrados pueden haber sido inaceptablemente ineficientes para los servidores industriales hace 10 años.

Los fabricantes de ordenadores lanzan con frecuencia nuevos modelos, a menudo con un mayor rendimiento . Los costes de software pueden ser bastante elevados, por lo que en algunos casos la forma más sencilla y barata de conseguir un mayor rendimiento puede ser simplemente comprar un ordenador más rápido, siempre que sea compatible con un ordenador existente.

Existen muchas formas de medir los recursos utilizados por 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 del disco, el uso del disco a largo plazo, 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 al algoritmo, es decir, la cantidad de datos a procesar. También pueden depender de la forma en que se organizan los datos; por ejemplo, algunos algoritmos de ordenación funcionan mal con datos que ya están ordenados o que están ordenados en orden inverso.

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

Análisis teórico

En el análisis teórico de algoritmos , la práctica normal es estimar su complejidad en sentido asintótico. La notación más comúnmente 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 como una función del tamaño de la entrada . La notación Big O es una medida asintótica de la complejidad de la función, donde aproximadamente significa que el requisito de tiempo para un algoritmo es proporcional a , omitiendo los términos de orden inferior que contribuyen menos que al crecimiento de la función a medida que crece arbitrariamente grande . Esta estimación puede ser engañosa cuando es pequeño, pero generalmente es suficientemente precisa cuando es grande ya que la notación es asintótica. Por ejemplo, la ordenación de burbuja puede ser más rápida que la ordenación 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 se escalen de manera eficiente a tamaños de entrada grandes, y la ordenación por fusión se prefiere a la ordenación de burbuja para listas de la longitud que se encuentran en la mayoría de los programas con uso intensivo de datos. n {\textstyle n} f ( n ) = O ( g ( n ) ) {\textstyle f(n)=O{\bigl (}g(n){\bigr )}} g ( n ) {\displaystyle g(n)} g ( n ) {\displaystyle g(n)} n {\textstyle n} n {\textstyle n} n {\textstyle n}

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

NotaciónNombreEjemplos
O ( 1 ) {\displaystyle O(1)} constanteEncontrar la mediana a partir de una lista ordenada de mediciones; utilizar una tabla de búsqueda de tamaño constante ; utilizar una función hash adecuada para buscar un elemento.
O ( log n ) {\displaystyle O(\log n)} logarítmicoEncontrar un elemento en una matriz ordenada con una búsqueda binaria o un árbol de búsqueda balanceado , así como todas las operaciones en un montón binomial .
O ( n ) {\displaystyle O(n)} linealEncontrar un elemento en una lista desordenada o en un árbol malformado (en el peor de los casos) o en una matriz desordenada; Sumar dos enteros de n bits mediante acarreo de ondulación .
O ( n log n ) {\displaystyle O(n\log n)} linealítmico , loglineal o cuasilinealRealizar una transformada rápida de Fourier ; ordenación por montículos , ordenación rápida ( mejor caso y promedio ) o ordenación por combinación
O ( n 2 ) {\displaystyle O(n^{2})} cuadráticoMultiplicar dos números de n dígitos mediante un algoritmo simple ; ordenamiento de burbuja (peor caso o implementación ingenua), ordenamiento de Shell , ordenamiento rápido ( peor caso ), ordenamiento por selección o ordenamiento por inserción
O ( c n ) , c > 1 {\displaystyle O(c^{n}),\;c>1} exponencialEncontrar la solución óptima (no aproximada ) al problema del viajante de comercio mediante programación dinámica ; determinar si dos afirmaciones lógicas son equivalentes mediante búsqueda de fuerza bruta

Medición del rendimiento

En el caso de las nuevas versiones de software o para ofrecer comparaciones con sistemas de la competencia, a veces se utilizan puntos de referencia que ayudan a medir el rendimiento relativo de un algoritmo. Si se crea un nuevo algoritmo de ordenación , por ejemplo, se puede comparar con sus predecesores para garantizar que, al menos, es tan eficiente como antes con los datos conocidos, teniendo en cuenta las mejoras funcionales. Los clientes pueden utilizar los puntos de referencia al comparar varios productos de proveedores alternativos para estimar qué producto se adaptará 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 patentados de empresas de software independientes, como Syncsort, compiten con productos de los principales proveedores, como IBM , por la velocidad.

Algunos puntos de referencia brindan oportunidades para producir un análisis que compare la velocidad relativa de varios lenguajes compilados e interpretados, por ejemplo, [3] [4] y The Computer Language Benchmarks Game compara el rendimiento de las 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 una variedad de criterios especificados por el usuario. Esto es bastante simple, como demuestra con un ejemplo el "Resumen del rendimiento de nueve lenguajes" de Christopher W. Cowell-Shah. [5]

Preocupaciones de implementación

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

Hay otros factores que pueden afectar las cuestiones de tiempo o espacio, pero que pueden estar fuera del control de un programador; estos incluyen la alineación de datos , la granularidad de los datos , la localidad de caché , la coherencia de 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 . [7]

Algunos procesadores tienen capacidades de procesamiento vectorial , que permiten que una sola instrucción opere en múltiples operandos ; puede que sea fácil o no para un programador o compilador utilizar estas capacidades. Los algoritmos diseñados para el procesamiento secuencial pueden necesitar ser rediseñados por completo para hacer uso del procesamiento paralelo , o podrían reconfigurarse fácilmente. A medida que la computación paralela y distribuida crece en importancia a fines 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 modelos. Esto a menudo presenta desafíos para optimizar los compiladores , que deben tener un amplio conocimiento de la CPU específica y otro hardware disponible en el destino de compilación para optimizar mejor un programa para el rendimiento. En el caso extremo, un compilador puede verse obligado a emular instrucciones no admitidas en una plataforma de destino de compilación, lo que lo obliga a generar código o vincular una llamada de biblioteca externa para producir un resultado que de otra manera sería incomputable en esa plataforma, incluso si es compatible de forma nativa y más eficiente en hardware en otras plataformas. Este suele ser el caso en sistemas integrados 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 producir cálculos de punto flotante.

Medidas de utilización de recursos

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

Las dos medidas más comunes son:

  • Tiempo : ¿cuánto tiempo tarda en completarse el algoritmo?
  • 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 necesaria para los datos sobre los que opera el código (uso de espacio intrínseco).

Para las computadoras cuya energía es suministrada por una batería (por ejemplo, computadoras portátiles y teléfonos inteligentes ), o para cálculos muy largos/grandes (por ejemplo, supercomputadoras ), otras medidas de interés son:

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

A partir de 2018 [update], el consumo de energía se ha convertido en una métrica importante para tareas computacionales de todo tipo y en todas las escalas, desde dispositivos integrados de Internet de las cosas hasta dispositivos de sistema en chip y granjas de servidores . Esta tendencia se conoce a menudo como computación verde .

Medidas menos comunes de eficiencia computacional también pueden ser relevantes en algunos casos:

  • Tamaño de transmisión : el ancho de banda puede ser un factor limitante. Se puede utilizar la compresión de datos para reducir la cantidad de datos que se van a transmitir. Mostrar una imagen (por ejemplo, el logotipo de Google ) puede dar como resultado la transmisión de decenas de miles de bytes (48 K en este caso) en comparación con la transmisión de seis bytes para el texto "Google". Esto es importante para las tareas informáticas limitadas por E/S .
  • Espacio externo : espacio necesario en un disco u otro dispositivo de memoria externa; podría ser para almacenamiento temporal mientras se ejecuta el algoritmo, o podría ser un almacenamiento a largo plazo necesario para futuras referencias.
  • 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 una computadora está dedicada a un algoritmo particular.

Tiempo

Teoría

El análisis de algoritmos , que normalmente utiliza conceptos como la complejidad temporal , se puede utilizar para obtener una estimación del tiempo de ejecución en función del tamaño de los datos de entrada. El resultado normalmente se expresa utilizando la notación Big O. Esto es útil para comparar algoritmos, especialmente cuando se debe procesar una gran cantidad de datos. Se necesitan estimaciones más detalladas para comparar el rendimiento del algoritmo cuando la cantidad de datos es pequeña, aunque es probable que esto sea de menor importancia. Los algoritmos paralelos pueden ser más difíciles de analizar .

Práctica

Se puede utilizar un punto de referencia para evaluar el rendimiento de un algoritmo en la práctica. Muchos lenguajes de programación tienen una función disponible que proporciona el uso del tiempo de CPU . En el caso de algoritmos de larga duración, el tiempo transcurrido también puede ser de interés. Por lo general, los resultados deben promediarse a lo largo de varias pruebas.

La creación de perfiles basada en 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 particular, un compilador y las opciones del compilador, por lo que los algoritmos que se comparan deben implementarse todos en las mismas condiciones.

Espacio

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

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

Los primeros ordenadores electrónicos y los primeros ordenadores domésticos 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 8 bits de memoria de trabajo. A finales de la década de 2010, lo habitual es que los ordenadores personales tengan entre 4 y 32 GB de RAM, lo que supone un aumento de más de 300 millones de veces la cantidad de memoria.

Jerarquía de memoria y caché

Las computadoras modernas pueden tener cantidades relativamente grandes de memoria (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 significativos:

Un algoritmo cuyas necesidades de memoria quepan en la memoria caché será mucho más rápido que un algoritmo que quepa en la memoria principal, que a su vez será mucho más rápida que un algoritmo que tenga que recurrir a la paginación. Debido a esto, las políticas de reemplazo de caché son extremadamente importantes para la computación de alto rendimiento, al igual que la programación consciente de la memoria caché y la alineación de datos . Para complicar aún más el problema, algunos sistemas tienen hasta tres niveles de memoria caché, con diferentes velocidades efectivas. Diferentes sistemas tendrán diferentes cantidades de estos diversos tipos de memoria, por lo que el efecto de las necesidades de memoria del algoritmo puede variar en gran medida de un sistema a otro.

En los primeros tiempos de la informática electrónica, 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 memoria, 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 ayudará a minimizar el tiempo. Esto se denomina principio de localidad y se puede subdividir en localidad de referencia , localidad espacial y localidad temporal . Un algoritmo que no cabe completamente en la memoria caché pero que muestra localidad de referencia puede funcionar razonablemente bien.

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 declaraciones go-to" (PDF) , Computing Surveys , 6 (4): 261–301, CiteSeerX 10.1.1.103.6084 , doi :10.1145/356635.356640, S2CID  207630080, archivado desde el original (PDF) el 24 de agosto de 2009 , consultado el 19 de mayo de 2013 
  3. ^ ab "Floating Point Benchmark: Comparing Languages ​​(Fourmilog: None Dare Call It Reason)". Fourmilab.ch. 4 de agosto de 2005. Consultado el 14 de diciembre de 2011 .
  4. ^ "Whetstone Benchmark History". Roylongbottom.org.uk . Consultado el 14 de diciembre de 2011 .
  5. ^ Personal de OSNews. "Resumen del rendimiento de nueve idiomas: evaluación comparativa de matemáticas y E/S de archivos". osnews.com . Consultado el 18 de septiembre de 2018 .
  6. ^ Kriegel, Hans-Peter ; Schubert, Erich; Zimek, Arthur (2016). "El arte (negro) 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.
  7. ^ Guy Lewis Steele, Jr. "Desmitificando el mito de las llamadas a procedimientos costosas, o implementaciones de llamadas a procedimientos consideradas dañinas, o Lambda: el GOTO definitivo". Laboratorio de IA del MIT. Memorándum del Laboratorio de IA AIM-443. Octubre de 1977.[1]
  8. ^ abcd 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 informática: un enfoque cuantitativo (sexta edición). Elsevier Science. ISBN 978-0128119051.OCLC 983459758  .
Retrieved from "https://en.wikipedia.org/w/index.php?title=Algorithmic_efficiency&oldid=1245928109"