Introducción: ¿Por qué la computadora "piensa" más rápido que el programador?
\n\nImagina: pasaste la noche escribiendo en un papel la secuencia de Collatz para el número 27. Y luego tu colega escribió un bucle en C++ que recorrió mil millones de números en un segundo. ¿Estás furioso? Espera, esto es solo el comienzo.
\n\nEn 2025, en Stack Overflow, una pregunta antigua pero muy reveladora ha resurgido con fuerza: "Why does C++ code for testing the Collatz conjecture run faster than hand-written assembly?". Pareciera que el ensamblador es un diálogo puro con el procesador, sin intermediarios. ¿Cómo es posible que un C++ de alto nivel supere al ensamblador escrito por un humano?
\n\nAnalicemos esta paradoja con el ejemplo de la conjetura de Collatz (también conocida como el problema 3n+1). Descubrirás por qué los compiladores modernos de C++ son verdaderas máquinas del tiempo y cómo escribir en 2025 un código que sea más rápido que el "control manual" de los registros.
\n\n¿Qué es la conjetura de Collatz y qué tiene que ver con C++?
\n\nRecordemos la esencia: tomamos cualquier número natural n. Si es par, lo dividimos entre 2. Si es impar, lo multiplicamos por 3 y le sumamos 1. La conjetura afirma que cualquier número, tarde o temprano, llegará a 1. La tarea del programador es verificar esto para el mayor rango posible de números.
La implementación clásica en C++ se ve así:
\n\n#include <iostream>\n\nint collatz_steps(long long n) {\n int steps = 0;\n while (n != 1) {\n if (n % 2 == 0) {\n n /= 2;\n } else {\n n = 3 * n + 1;\n }\n ++steps;\n }\n return steps;\n}\n\nint main() {\n long long max_steps = 0;\n long long best_num = 0;\n for (long long i = 1; i <= 1000000; ++i) {\n int s = collatz_steps(i);\n if (s > max_steps) {\n max_steps = s;\n best_num = i;\n }\n }\n std::cout << "Número: " << best_num << " pasos: " << max_steps;\n}\n\n\nParece algo sencillo. Pero fue precisamente este código simple el que originó la legendaria disputa con el ensamblador.
\n\n¿Por qué pierde el ensamblador? Los principales errores de 2025
\n\nCuando un desarrollador escribe en ensamblador, a menudo piensa: "Conozco cómo funciona el procesador, escribiré algo perfecto". Pero los procesadores modernos no son simples ejecutores de instrucciones. Son complejos conductos superescalares con predicción de saltos, ejecución especulativa y memoria caché. El compilador de C++ (GCC, Clang, MSVC) conoce todo sobre su arquitectura objetivo.
\n\nError n.º 1: Ignorar la optimización de bifurcaciones
\n\nEn el ensamblador manual, el programador a menudo escribe las bifurcaciones "directamente":
\n; Pseudo-ensamblador\ncheck_even:\n test rax, 1\n jnz is_odd\n shr rax, 1\n jmp next\nis_odd:\n mov rbx, rax\n shl rax, 1\n add rax, rbx\n inc rax\nnext:\n ...\n\nEl compilador de C++ con la bandera -O3 actúa con más astucia. Utiliza CMOV (conditional move) y magia con bits. En lugar de bifurcaciones, usa aritmética:
; GCC 14 generará algo como:\n mov rcx, rax\n shr rcx, 1 ; dividir entre 2 (si es par)\n and eax, 1 ; verificar el bit menos significativo\n lea rdx, [rax + rcx*2 + 1] ; cálculo ingenioso para impar\n test eax, eax\n cmove rax, rcx ; si es par - tomar la división\n cmovne rax, rdx ; si es impar - tomar 3n+1\n\n\nResultado: sin paradas en el conducto debido a saltos mal predichos. En 2025 esto es crítico, ya que el predictor de saltos en la CPU se ha vuelto más inteligente, pero aún no es perfecto para números aleatorios.
\n\nError n.º 2: Olvidar la vectorización (AVX-512)
\n\nLos compiladores modernos de C++ (especialmente Clang 18+ y GCC 14+) vectorizan automáticamente los bucles. Si escribes un bucle de verificación simple, el compilador puede procesar hasta 8 números por ciclo de reloj, usan