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:
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.
| 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.
- 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. - Versión con conjunto: mete primero los identificadores de los clientes activos en un
Set(50.000 operaciones) y luego consulta cada pedido conhas(), que de media cuesta una operación: unas 100.000 en total. - Diferencia: unas 25.000 veces menos operaciones, con el mismo resultado.
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
mapyfilter, 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.