¿Es < más rápido que <= en C++? Desmentimos mitos con ejemplos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: Un mito que vive durante décadas

\n\n

Si alguna vez has mirado en Stack Overflow la sección de C++, seguro que has visto la pregunta: «¿Es < más rápido que <=?». Esta pregunta ha acumulado miles de visitas y decenas de páginas de discusión. Y no es de extrañar: el tema de la optimización del código cautiva las mentes de programadores de todos los niveles.

\n\n

La lógica parece de hierro: el operador < compara dos números y devuelve el resultado, mientras que <= hace lo mismo, pero además verifica la igualdad. Por lo tanto, < debería ser más rápido, ¿verdad? Esto es especialmente importante en bucles, donde la operación de comparación se ejecuta millones de veces.

\n\n

Hoy vamos a desentrañar este mito de una vez por todas. Aprenderás:

\n
    \n
  • Cómo funcionan realmente los operadores a nivel de procesador.
  • \n
  • Por qué el compilador es tu mejor amigo (o enemigo, si no confías en él).
  • \n
  • Cuándo la microoptimización tiene sentido y cuándo es una pérdida de tiempo.
  • \n
\n\n

¿Cómo funcionan los operadores < y <= a nivel de ensamblador?

\n\n

Para entender si hay alguna diferencia, hay que mirar «bajo el capó»: en lo que el compilador convierte tu código C++. Consideremos dos funciones simples:

\n\n
bool less_than(int a, int b) {\n    return a < b;\n}\n\nbool less_or_equal(int a, int b) {\n    return a <= b;\n}
\n\n

Las compilaremos con g++ (sin optimizaciones, bandera -O0). Esto es lo que vemos en el listado de ensamblador (x86-64):

\n\n
less_than(int, int):\n    cmp    edi, esi      ; comparar a y b\n    setl   al            ; establecer al=1 si a < b (menor que)\n    ret\n\nless_or_equal(int, int):\n    cmp    edi, esi      ; comparar a y b\n    setle  al            ; establecer al=1 si a <= b (menor o igual que)\n    ret
\n\n

¿Ves? La única diferencia son las instrucciones setl (set if less) y setle (set if less or equal). Ambas instrucciones se ejecutan en un ciclo de reloj del procesador. En las CPU modernas (Intel Core i7, AMD Ryzen) tienen una latencia idéntica: 1 ciclo.

\n\n

Conclusión n.º 1: A nivel de instrucciones de máquina, no hay diferencia de rendimiento entre < y <=. Punto.

\n\n

¿Y qué pasa con los bucles? ¡Ahí debería haber diferencia!

\n\n

La mayoría de las veces, el debate se centra en los bucles. Compara estos dos enfoques:

\n\n
// Opción 1: con <\nfor (int i = 0; i < n; ++i) {\n    // hacer algo\n}\n\n// Opción 2: con <=\nfor (int i = 0; i <= n - 1; ++i) {\n    // hacer lo mismo\n}
\n\n

La intuición sugiere: la segunda opción hace un cálculo extra n - 1. Pero veamos qué sucede después de la optimización del compilador.

\n\n

Ejecutemos un benchmark con g++ -O2 para n = 1 000 000 000:

\n\n
#include <iostream>\n#include <chrono>\n\nint main() {\n    const long long n = 1'000'000'000;\n    volatile long long sink = 0; // para que el compilador no elimine el bucle\n\n    auto start = std::chrono::high_resolution_clock::now();\n    \n    for (long long i = 0; i < n; ++i) {\n        sink += i;\n    }\n    \n    auto end = std::chrono::high_resolution_clock::now();\n    std::cout << "Tiempo (<): " \n              << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()\n              << " ms\\n";\n\n    start = std::chrono::high_resolution_clock::now();\n    \n    for (long long i = 0; i <= n - 1; ++i) {\n        sink += i;\n    }\n    \n    end = std::chrono::high_resolution_clock::now();\n    std::cout << "Tiempo (<=): " \n              << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()\n              << " ms\\n";\n\n    return 0;\n}
\n\n

Resultados en mi máquina (i7-107

Blogs

Book Recommendations