Módulo 03 // Unidad 01

Eficiencia algorítmica y consumo

Por qué la complejidad de un algoritmo acaba siendo energía, memoria y hardware

Unidad para alumnos del curso

Comprobando tu sesión…

Inicia sesión con la cuenta de Google con la que estás matriculado para ver esta unidad.

Esa cuenta no tiene acceso a este curso. Prueba con otra.

No se ha podido cargar el inicio de sesión de Google. Comprueba la conexión o si algún bloqueador lo impide, y recarga la página.

Empieza el módulo del backend, y empieza por lo más cercano al código: los algoritmos. En segundo curso ya sabes que un algoritmo puede ser más o menos eficiente. Lo que quizá no te habían contado es que esa eficiencia no es sólo un asunto de rapidez: cada operación innecesaria es electricidad, calor y, a la larga, hardware.

1 // Energía es potencia por tiempo

Un procesador trabajando consume una potencia más o menos estable, medida en vatios. La energía que gasta una tarea sale de multiplicar esa potencia por el tiempo que dura:

Energía (Wh) = Potencia (W) × Tiempo (h)Si la potencia no cambia mucho, la forma más directa de ahorrar energía es terminar antes.

De ahí la regla más útil de este módulo: en un servidor, el tiempo de CPU es el mejor indicador del consumo. Un código que hace menos operaciones termina antes, deja libre el procesador para otras peticiones y, cuando se suma a todo el sistema, permite atender la misma carga con menos servidores. Es la eficiencia energética y la eficiencia de hardware del módulo 2, a la vez.

Existe una excepción conocida, race to idle: a veces compensa usar más potencia durante menos tiempo, porque el procesador vuelve antes a un estado de bajo consumo. No cambia la regla general: menos trabajo total es menos energía. Lo que cambia es cómo se reparte.

2 // La O grande, leída en vatios

La notación O grande describe cómo crece el número de operaciones de un algoritmo cuando crece la cantidad de datos (n). Normalmente se estudia para saber si un programa será rápido. Aquí la vas a leer de otra manera: cuántas operaciones, y por tanto cuánta energía, cuesta cada dato más.

OPERACIONES (≈ ENERGÍA) SEGÚN LOS DATOS CPU·s pocos datos: no se nota O(n²) O(n log n) O(n) cantidad de datos (n) → cada operación de más es tiempo de CPU
Con pocos datos las tres curvas están juntas y la diferencia no se nota. Fíjate en cómo se separan cuando n crece: ahí es donde un mal algoritmo se convierte en consumo.
Complejidad Ejemplo típico Operaciones con n = 1.000 Con n = 1.000.000
O(1) Acceder a un mapa por clave 1 1
O(log n) Búsqueda binaria, índice de base de datos ~10 ~20
O(n) Recorrer una lista una vez 1.000 1.000.000
O(n log n) Buen algoritmo de ordenación ~10.000 ~20.000.000
O(n²) Dos bucles anidados sobre los mismos datos 1.000.000 1.000.000.000.000

Los números son aproximados (la notación ignora constantes), pero la última fila lo dice todo: con un millón de datos, un algoritmo cuadrático hace un billón de operaciones. Si cada una tardara un nanosegundo, serían unos 17 minutos de CPU a plena carga, frente a milisegundos de la versión lineal.

Una API recibe una lista de 50.000 pedidos y otra de 50.000 clientes, y tiene que comprobar qué pedidos pertenecen a clientes activos.

  1. Versión ingenua: para cada pedido, recorre la lista de clientes con includes(). Son 50.000 × 50.000 = 2.500 millones de comparaciones en el peor caso.
  2. Versión con conjunto: mete primero los identificadores de los clientes activos en un Set (50.000 operaciones) y luego consulta cada pedido con has(), que de media cuesta una operación: unas 100.000 en total.
  3. Diferencia: unas 25.000 veces menos operaciones, con el mismo resultado.
Un cambio de dos líneas pasa de segundos de CPU a milisegundos. Si esa comprobación se hace en cada petición, la versión ingenua puede obligar a tener varios servidores donde bastaría uno.

3 // Memoria, estructuras y lenguajes

La CPU no es el único recurso. La memoria RAM consume energía por el mero hecho de estar ocupada y, sobre todo, determina cuánto servidor necesitas. Algunas fuentes habituales de derroche:

  • Cargar datos enteros cuando basta con una parte: leer un fichero de 2 GB en memoria para contar sus líneas, en lugar de procesarlo por trozos (streaming).
  • Copias innecesarias: transformar una lista cinco veces seguidas con map y filter, creando cinco listas intermedias, cuando un solo recorrido bastaría.
  • Objetos que nunca se liberan: cachés sin límite o listeners que se acumulan. La memoria crece hasta que hay que reiniciar o comprar más.
  • Recolección de basura constante: crear y descartar millones de objetos pequeños obliga al recolector a trabajar sin parar, y eso también es CPU.

Elegir la estructura de datos adecuada suele ser la mayor mejora con el menor esfuerzo: mapas y conjuntos para buscar, colas para procesar en orden, arrays cuando accedes por posición.

¿Y el lenguaje? Un estudio académico muy citado comparó el consumo de energía de decenas de lenguajes resolviendo los mismos problemas, y los compilados como C, Rust o C++ salieron mucho más eficientes que los interpretados como Python o Ruby. Tómalo con cautela: son programas de prueba, no aplicaciones web reales, donde buena parte del tiempo se pasa esperando a la base de datos o a la red. Un buen algoritmo en un lenguaje lento suele ganar a uno malo en un lenguaje rápido.

4 // Dónde merece la pena optimizar

Optimizarlo todo es imposible y, además, contraproducente: el código ilegible también tiene un coste. La clave es medir primero y optimizar donde se concentra el trabajo:

  • Lo que se ejecuta muchas veces: el código de cada petición, los bucles internos, las funciones que llaman todas las demás.
  • Lo que crece con los datos: cualquier cosa que hoy procese cien registros y mañana procese un millón.
  • Lo que se ejecuta sin que nadie lo mire: tareas programadas y procesos en segundo plano, que verás al final del módulo.

Para encontrar esos puntos existen los perfiladores (profilers): herramientas que muestran en qué funciones se va el tiempo de CPU. Node.js, Java, PHP y Python tienen los suyos, y las DevTools del navegador incluyen uno para el código de cliente.

En un servidor, tiempo de CPU y memoria son energía y hardware. Elige bien la complejidad y la estructura de datos, mide con un perfilador antes de optimizar y concentra el esfuerzo en el código que más se repite.

Autoevaluación

¿Por qué un algoritmo que termina antes consume menos energía, si el procesador trabaja igual de fuerte?
Porque la energía es potencia multiplicada por tiempo. Si la potencia es parecida, lo que decide el consumo es cuánto tiempo pasa el procesador trabajando. Además, un servidor que termina antes cada tarea puede atender más tareas, y hacen falta menos servidores para la misma carga.
Un algoritmo O(n²) y uno O(n) tardan lo mismo con 100 elementos. ¿Por qué debería importarte cuál eliges?
Porque la diferencia crece con los datos, y los datos casi siempre crecen. Con 100 elementos puede no notarse, pero con 100.000 el cuadrático hace del orden de 100.000 veces más operaciones. Lo que hoy es invisible se convierte en minutos de CPU, y en energía, cuando la aplicación crece.
¿Por qué usar un Set o un mapa en lugar de buscar en una lista puede ahorrar energía?
Porque cambia una búsqueda que recorre todos los elementos por una que va casi directa al resultado. Buscar en una lista es O(n); en un conjunto basado en hash es, de media, O(1). Dentro de un bucle, esa diferencia convierte un algoritmo cuadrático en uno lineal, con mucho menos tiempo de CPU.