Descargar

Rendimiento

Cómo se compila hyper

  • **C++20 con -O2/-O3** (GCC en Windows con MSYS2 UCRT64; GCC o Clang en Linux y macOS).
  • LTO (optimización en tiempo de enlace): todo el programa se optimiza junto.
  • PGO (optimización guiada por perfil, solo con GCC): build.ps1 compila una versión instrumentada, la entrena ejecutando las pruebas, los ejemplos y los benchmarks, y recompila con esos datos. El compilador sabe así qué ramas son las habituales y cómo ordenar el código del intérprete.
./build.ps1          con PGO (como las versiones publicadas)
./build.ps1 -NoPGO   la mitad de tiempo de compilación

Benchmarks

tests/bench/bench.hz mide seis casos típicos; tests/bench/minbench.sh lo ejecuta varias veces y se queda con el mínimo:

sh tests/bench/minbench.sh build/hyper.exe 7
CasoQué haceantes de 0.40.4.00.5.00.6.00.7.0
fibfib(27) recursivo con parámetros : number (≈630.000 llamadas)44 ms42 ms25 ms18 ms18 ms
bucle3 millones de vueltas con % y una variable global118 ms80 ms72 ms72 ms72 ms
objetos300.000 llamadas a un método que modifica dos campos52 ms31 ms21 ms17 ms18 ms
vec300.000 sumas de Vec2 (crea dos objetos por vuelta)69 ms61 ms62 ms18 ms17 ms
listasmap + filter + sum sobre 200.000 elementos31 ms26 ms25 ms25 ms25 ms
textoconcatenar 20.000 veces a un texto creciente282 ms238 ms151 ms148 ms121 ms

Medido en el mismo equipo (Windows 11, versiones compiladas con build.ps1). Los números absolutos dependen del equipo; lo importante es la evolución.

Qué mejoró en cada versión

0.5.0: PGO; los textos se procesan una sola vez al internarlos (antes se calculaba su hash tres veces).

0.6.0:

  • llamadas directas a funciones y métodos del script cuando los argumentos encajan;
  • comprobación de tipos de parámetros sin copiar ni comparar textos (y una instrucción propia para : number);
  • comparación y salto fusionados en una instrucción;
  • bucle for numérico con una sola instrucción por vuelta;
  • asignador de objetos pequeños con listas libres (vec es 3,4 veces más rápido).

En total, de la primera versión a la 0.6.0: llamadas 2,4 veces más rápidas, objetos 3 veces, vectores 3,8 veces, bucles 1,6 veces y textos 1,9 veces.

0.7.0:

  • hash por muestras de los textos largos al internarlos: concatenar a un texto que crece es un 18 % más rápido (texto: 148 → 121 ms). El coste que queda es la copia del texto en cada +: para textos muy largos sigue siendo mejor juntar las partes con join;
  • los for numéricos cuya variable no captura ninguna función ya no crean y destruyen la variable en cada vuelta (FOR_CHECK_V/FOR_NEXT_V): un bucle vacío de 3 millones de vueltas pasa de 19 a 10 ms (medido con la misma compilación sin PGO antes y después del cambio);
  • las asignaciones usadas como sentencia guardan y descartan en una instrucción (DEFINE_GLOBAL, SET_LOCAL_POP);
  • cada operación aritmética tiene su propio caso en el intérprete (sin un segundo switch) y % usa división de 32 bits cuando los números caben.

En los seis casos de bench.hz, compilados con PGO, las tres últimas optimizaciones apenas se notan: el compilador ya colocaba bien ese código con el perfil. En bucle, lo que más pesa es el propio % (convertir los números a enteros y dividir cuesta unos 9 ns), no las instrucciones de alrededor. Los siguientes pasos para el intérprete están en la hoja de ruta.

Para ver qué instrucciones genera tu programa: hyper bytecode archivo.hz.

Consejos para programas rápidos

  • Dentro de funciones, las variables locales son más rápidas que las globales.
  • Anotar los parámetros con tipos no ralentiza (y ayuda a encontrar errores).
  • Para cálculos numéricos grandes usa Matrix: opera en C++ sobre bloques de memoria.
  • Construir un texto muy largo con += copia el texto cada vez; acumula las partes en una lista y usa join("") al final.
  • Time.measure(f) dice cuánto tarda una función.