Evaluación comparativa de 20 lenguajes de programación en N-queens y multiplicación de matrices

Las pruebas comparativas que comparan 20 lenguajes de programación en N-queens y multiplicación de matrices están generando debate sobre qué miden realmente estos tests: velocidad bruta del lenguaje/runtime, calidad del compilador o la fortaleza de ecosistemas como NumPy y BLAS. Los comentaristas destacan cómo implementaciones ingenuas y sin bibliotecas pueden hacer que Python y otros lenguajes dinámicos parezcan desproporcionadamente lentos frente a C, Julia o Nim, y cuestionan si el calentamiento del JIT, el diseño de memoria y el código no idiomático sesgan los resultados. Muchos sostienen que, aunque estos microbenchmarks son útiles para entender características de rendimiento de bajo nivel, dicen poco sobre los flujos de trabajo reales, donde predominan bibliotecas optimizadas, solucionadores y herramientas específicas de dominio.

Objetivos y metodología de las pruebas

  • Las pruebas cubren N-queens, multiplicación de matrices, sudoku y bed coverage, con implementaciones en su mayoría escritas a mano y “naive”, a menudo inspiradas en ejemplos de estilo Rosetta.
  • Algunos consideran que esto es útil para medir el rendimiento base de un lenguaje o VM cuando tienes que implementar un algoritmo nuevo por tu cuenta.
  • Otros argumentan que el código es subóptimo y no idiomático en varios lenguajes, así que los resultados pueden decir más sobre implementaciones concretas que sobre los lenguajes.
  • Hay debate sobre si incluir el calentamiento del JIT y el tiempo de inicio; algunos sostienen que “desde la CLI hasta el resultado” es lo realista, mientras que otros prefieren JIT ya calentados o contar también el tiempo de compilación en lenguajes AOT.

Bibliotecas frente a rendimiento “puro del lenguaje”

  • Un debate importante cuestiona excluir bibliotecas como NumPy, BLAS o bibliotecas especializadas de tensores.
  • Una postura: las pruebas deberían comparar lenguajes sin bibliotecas externas de C/Fortran; de lo contrario solo se mide la sobrecarga de FFI.
  • La otra postura: el Python numérico idiomático, C#, etc. siempre usan esas bibliotecas; una multiplicación de matrices puramente en el lenguaje es irreal y tergiversa el rendimiento del mundo real.
  • Se señala que prácticamente toda la multiplicación de matrices de alto rendimiento en cualquier lenguaje depende en última instancia de bibliotecas muy optimizadas, a menudo con ensamblador, y que Python suele ser “rápido” solo cuando llama a ellas.

Dificultad de la multiplicación de matrices y BLAS

  • Varios comentarios subrayan que una multiplicación de matrices realmente al nivel de BLAS requiere un tiling cuidadoso, kernels SIMD, conscientes de caché y registros, y a veces ensamblador; los compiladores no alcanzarán >90% del pico con un simple triple bucle.
  • Algunos afirman que se puede igualar o superar a BLAS para formas o patrones de dispersión específicos con el control de nivel C++/Nim; otros discrepan, citando experimentos en los que código escrito a mano quedó muy por detrás de OpenBLAS salvo con un esfuerzo considerable.
  • Se cita un proyecto en Nim con pruebas comparativas que muestran un rendimiento comparable a OpenBLAS y MKL en varias CPU, usando microkernels SIMD generados y threading personalizado.

Observaciones específicas de cada lenguaje

  • Rust: la multiplicación inicial era lenta por el diseño Vec<Vec> y las comprobaciones de límites; las implementaciones basadas en iteradores o de tamaño estático ahora igualan a C.
  • C#: la primera multiplicación usaba arrays rectangulares (multiplicaciones extra por acceso). Cambiar a array de arrays acerca el rendimiento al de Java; se esperan más mejoras con SIMD (Vector<T>/System.Numerics.Tensors).
  • Swift: la multiplicación puede igualar a C/Rust tras optimización, pero sudoku sigue siendo lento por muchas asignaciones en heap y falta de arrays estáticos.
  • Julia: la multiplicación inicial usaba la orientación de memoria incorrecta y desactivaba SIMD; el código corregido rinde cerca de C.
  • Mojo: alrededor de 2× más lento que C en multiplicación de matrices, visto por algunos como aceptable dada la ergonomía, aunque otros lo consideran demasiado verboso frente a Julia.

JIT, arranque y hardware

  • Algunos sostienen que los lenguajes con JIT están en desventaja por ejecuciones en frío; otros responden que muchos usos reales (CLIs, compilaciones) son de corta duración, así que el rendimiento en frío importa.
  • Los sistemas ARM big.LITTLE plantean dudas sobre si las pruebas se ejecutan en núcleos de rendimiento o de eficiencia.
  • Los detalles mono-, multihilo y de vectorización (por ejemplo, threading de OpenBLAS, uso de AVX/AVX-512, comprobaciones de límites) afectan significativamente a las diferencias observadas.

Gráficas y métricas alternativas

  • Los gráficos de barras apiladas ocultan comparaciones cuando unos pocos lenguajes (PHP, Ruby, Perl, CPython puro) son extremadamente lentos; se sugieren gráficas separadas, escalas logarítmicas u ops/sec.
  • Algunos quieren métricas adicionales como el tamaño del código gzipped o el LOC para reflejar expresividad y complejidad; otros señalan la dificultad de definir una métrica de tamaño justa y centrada en humanos.
  • Varios comentaristas mencionan suites de pruebas más amplias (por ejemplo, el Computer Language Benchmarks Game) y piden más tareas del mundo real (E/S de archivos, análisis de JSON, servidores).