Añadir una línea más al código no solo no ralentiza el programa, sino que lo hace cuatro veces más rápido. Suena a leyenda urbana, pero el 12 de julio de 2026 un programador llamado purplesyringa documentó en su blog este fenómeno que él mismo había verificado.
En aquel momento estaba escribiendo un programa de compresión de datos. Dentro había un bucle muy corto, con una sola línea de código central: consultar repetidamente el siguiente valor en una tabla y luego almacenar lo consultado. Solo esa línea, limpia y directa. Pero el programa corría desesperantemente lento. Probó todo tipo de optimizaciones convencionales, sin buenos resultados. Al final hizo algo que incluso a él mismo le parecía absurdo: añadió una comprobación if que parecía completamente redundante, comprobar «si el valor recién consultado y el valor actual son iguales»; si eran distintos, actualizaba; si eran iguales, saltaba.
El grado de «palabrería inútil» de esa línea if equivale más o menos a esto: ya sabes que llevas cien euros en el bolsillo, pero aun así metes la mano para tocarlos, confirmar que realmente están allí, y solo entonces sales por la puerta. La añadas o no, sigues teniendo cien euros en el bolsillo. Pero lo asombroso es que, tras añadirla, el programa pasó de correr en 320 microsegundos a correr en 80 microsegundos: cuatro veces más rápido, ni más ni menos.
La primera vez que leí este caso, a mí también me pareció un chiste. Pero no es magia negra. Detrás se esconde una historia sobre cómo las computadoras modernas «adivinan» las respuestas.
El cuello de botella en la cadena de montaje de la fábrica
Para entender esto, hay que saber primero cómo trabaja una CPU.
Imagina la CPU como una cadena de montaje de una fábrica. Los operarios de la cadena no esperan a que un producto esté completamente ensamblado para empezar el siguiente: eso sería demasiado lento. En vez de eso, dividen el trabajo en muchos pasos pequeños: cortar, pulir, pintar, control de calidad… cada estación procesa un producto distinto al mismo tiempo. Así, la velocidad de producción de toda la línea depende de «la estación más lenta», y no de «terminar uno antes de empezar el siguiente». Esto es el «paralelismo a nivel de instrucción» de las CPU modernas: procesar varias instrucciones a la vez y aumentar enormemente la eficiencia.
Pero la cadena de montaje tiene un punto débil fatal: si lo que va a ser el siguiente producto depende de haber terminado de procesar el anterior, toda la cadena se atasca. Los operarios solo pueden quedarse esperando.
En el código de purplesyringa la situación era exactamente esa. Su bucle era: j = next_j[i][j]: usar el valor actual j para consultar la tabla, obtener el siguiente j, y volver a usar ese nuevo j para la siguiente vuelta. Cada vuelta depende del resultado de la anterior. Los operarios de la cadena de la CPU esperan angustiados a que la estación anterior entregue, y esa estación a su vez espera a la de más arriba… toda la línea se convierte en un atasco de tráfico de un solo carril. Este es el cuello de botella de latencia causado por la llamada «cadena de dependencia de datos».
Un sistema de navegación que «adivina el camino»
Pero las CPU modernas tienen una habilidad especial, justo la adecuada para lidiar con esta situación. Se llama «predictor de ramas».
Sigamos con la metáfora de la fábrica: en la cadena hay una estación de control de calidad, y el operario decide, según el resultado de la inspección, si el producto va por el canal A o por el B. Si cada vez esperara a terminar la inspección para elegir el canal, la cadena seguiría atascándose. Así que la fábrica instaló un «sistema de experiencia histórica»: cada vez que se llega a esta estación de control, el sistema adivina, basándose en las 99 elecciones anteriores, que esta vez lo más probable es que también vaya por el A. El operario empuja el producto por adelantado hacia el canal A. Si acierta, la cadena fluye sin parar; si se equivoca, retira el producto semielaborado que ya había metido en el canal A y lo reencamina de nuevo por el canal B.
El predictor de ramas de la CPU es justamente este sistema. Registra las elecciones que el programa hizo en el pasado en cada «bifurcación» y luego usa un conjunto de circuitos complejos para predecir hacia dónde irá la próxima vez. La precisión de predicción de ramas de las CPU modernas suele superar el 95 %, más alta que la precisión con la que la mayoría de los humanos toma decisiones.
La perspicacia de purplesyringa fue esta: aunque en su código no había ninguna «bifurcación» evidente (no había if-else), la propia cadena de dependencia de datos era una «espera» invisible. Tuvo un destello de inspiración: ¿y si introdujera una bifurcación explícita para que el predictor de ramas entrara en juego?
La verdadera función de esa línea «inútil»
La línea if que añadió tenía esta lógica: comprobar si el resultado de la consulta a la tabla y el valor actual son distintos; si son iguales, no hacer nada; solo si son distintos, actualizar. Como la inmensa mayoría de las veces el valor consultado y el valor actual sí son iguales, el predictor de ramas de la CPU «aprendió» rápidamente que el cuerpo de este if casi nunca se ejecuta.
Así que la CPU se atreve a adivinar: en la próxima vuelta también se saltará el cuerpo del if. Y si adivina que lo saltará, entonces no necesita esperar el resultado de la vuelta anterior: simplemente supone que j no ha cambiado y sigue corriendo hacia adelante. La cadena de montaje vuelve a moverse. Múltiples vueltas del bucle pueden procesarse en paralelo.
Cuando ocasionalmente el resultado de la consulta a la tabla es realmente distinto, el predictor de ramas descubre que se equivocó, descarta el producto semielaborado que había ido por el camino equivocado y vuelve a correr esa vuelta con el valor de j correcto. Este proceso se llama «penalización por fallo de predicción de rama». Pero como la proporción de aciertos fallidos es extremadamente baja, este pequeño coste es mucho menor que el de esperar en seco todo el tiempo.
El resultado es este: una sentencia if que parece completamente redundante le dio al predictor de ramas una señal de que «se puede adivinar». Convirtió una cadena de dependencia que solo podía ejecutarse en serie en una cadena de montaje que puede ejecutarse de forma especulativa y en paralelo.
Cuando la «buena intención» del compilador estropea las cosas
La historia, hasta aquí, solo se ha contado a medias. Hay otro adversario aún más molesto: el compilador.
El compilador es el programa encargado de traducir el código legible por humanos que escribe el programador a instrucciones de máquina que la CPU puede ejecutar. Los compiladores modernos son muy inteligentes, tanto que identifican automáticamente el «código inútil» y lo eliminan directamente. A ojos del compilador, la línea if que añadió purplesyringa decía «actualiza A solo si A no es igual a A», lo cual, por supuesto, es palabrería. El compilador soltó una risita fría y la optimizó, es decir, la eliminó.
El programador quería engañar al predictor de ramas de la CPU, pero el compilador le confiscó primero el objeto con el que pensaba hacer el truco.
Este es el sentido de las «decisiones conservadoras» del título de este artículo, y también lo que a mí me parece lo más sustancioso de este caso: el compilador respeta estrictamente el principio de «no cambiar la semántica del programa»: si lo que escribes es lógicamente inútil, no te lo traduzco. Pero lo que el compilador no sabe es que el verdadero valor de cierto código se esconde a nivel del hardware: le proporciona a la CPU una señal para poder ejecutar de forma especulativa.
En realidad es un duelo a tres bandas. La CPU es agresiva: adivina a toda costa, buscando la manera de adelantar el trabajo. El compilador es conservador: respeta estrictamente la semántica, no hace ni de más ni de menos. Y el programador está en medio: quiere aprovechar la agresividad de la CPU y a la vez tiene que engañar el conservadurismo del compilador.
El precinto de «no toques aquí»
La solución que encontró purplesyringa fue usar una palabra clave del lenguaje C llamada volatile. Esta palabra equivale, en C, a pegarle al compilador un precinto de «no toques aquí»: le dice al compilador que estos datos pueden cambiar sin que él lo sepa, así que no los optimice y los lea fielmente cada vez.
Tras pegar este precinto, el compilador dejó de considerar que la condición del if era la palabrería de «nunca se cumple», y por tanto la conservó. Con el if a salvo, el predictor de ramas ya tenía algo que adivinar, y la cadena de montaje pudo volver a operar en paralelo.
Más tarde, en la discusión de la comunidad Lobsters, otro programador, ibookstein, descubrió que usar la anotación [[unlikely]] de C++20 (equivalente a decirle explícitamente al compilador «por esta rama se pasa muy pocas veces») también lograba un efecto similar. No obstante, purplesyringa señaló que el método del precinto volatile genera código de máquina de mejor calidad y, además, no se limita a un compilador concreto.
Un concepto más amplio: value speculation
En el hilo de discusión de Lobsters, alguien señaló que este truco tiene en realidad un nombre formal: «value speculation». La idea central es esta: cuando disponemos de un método heurístico que «con alta probabilidad acierta» el valor de algo, podemos aprovechar el predictor de ramas para ejecutar de forma especulativa y así romper la cadena de dependencia de datos.
Este concepto puede rastrearse hasta investigaciones y blogs anteriores (el trabajo de Paul Khuong, Per Vognsen y otros). En un artículo clásico de mazzo.li se usa el mismo truco para acelerar el recorrido de listas enlazadas: al recorrer una lista enlazada, la dirección del siguiente nodo depende del puntero almacenado en el nodo actual, lo cual es también una cadena de dependencia de datos. Pero si adivinamos que «el siguiente nodo está en memoria justo pegado a la posición del nodo actual», podemos hacer que la CPU haga prefetch por adelantado, elevando el rendimiento de 14 GB/s a 45 GB/s (cuando los datos están en la caché de la CPU).
El truco del if de purplesyringa y la value speculation son, en esencia, lo mismo: sustituir una espera cara por una conjetura barata.
Qué está en tu contra
Lo más interesante de este asunto es que revela tres capas de conflicto entre «lo que crees» y «lo que realmente ocurre»:
Primera capa: la intuición humana cree que «cuanto menos código, más rápido corre». Pero en este caso, añadir una línea de código resulta más rápido, porque la función de esa línea es enviar una señal, no realizar un cálculo.
Segunda capa: el compilador cree que «el código lógicamente inútil debe eliminarse». Pero la utilidad de cierto código se esconde a nivel del comportamiento del hardware, no a nivel de la semántica lógica.
Tercera capa: normalmente creemos que «adivinar mal tiene un coste, así que mejor no adivinar». Pero la filosofía de diseño de las CPU modernas es justo la contraria: adivina con audacia; si aciertas, ganas; si te equivocas, a lo sumo lo tiras todo y vuelves a empezar. Mientras la probabilidad de acertar sea suficientemente alta, en conjunto sales ganando.
Esta historia no tiene un final de gran narrativa. Es solo un hecho contraintuitivo con el que un programador se topó por casualidad mientras optimizaba un algoritmo de compresión. Pero a través de esta pequeña sentencia if puedes ver una verdad sutil del nivel más bajo de las computadoras modernas: la CPU es un apostador, el compilador es un abogado, y los mejores programadores suelen ser quienes saben cuándo engañar al abogado y pasarle la información al apostador.
Fuentes de referencia
- Blog de purplesyringa: Quadrupling code performance with a “useless” if (12 de julio de 2026; el artículo original recoge todos los detalles técnicos, ejemplos de código y datos de rendimiento)
- Discusión en la comunidad Lobsters (s/1an425): 104 puntos, 14 comentarios, incluyendo la alternativa
[[unlikely]]descubierta por ibookstein y el enlace al concepto de «value speculation» señalado por mikejsavage - mazzo.li: Beating the L1 cache with value speculation (julio de 2021; describe en detalle la aplicación de la técnica de value speculation en el recorrido de listas enlazadas, con gráficas comparativas de rendimiento)