El desafío de mil millones de filas
Un “Desafío de mil millones de filas” centrado en Java para calcular el mínimo/promedio/máximo de temperaturas a partir de un archivo de texto de 12 GB y 1.000 millones de líneas se ha convertido en una muestra de ajuste extremo del rendimiento y conocimiento de sistemas. Los participantes debaten si la tarea está limitada por I/O o por CPU, alternando técnicas como mapeo de memoria, análisis numérico personalizado, SIMD, multi-threading y el aprovechamiento del comportamiento de la caché de páginas del SO, mientras también discuten métodos de benchmarking justos y reglas que prohíben trucos específicos del conjunto de datos. Muchos contrastan Java optimizado a mano con C, Rust, SQL, bases de datos, awk y pandas, usando el ejercicio para explorar cómo se comportan el hardware moderno, los runtimes y las herramientas de procesamiento de datos bajo cargas pesadas, pero conceptualmente simples.
Alcance del desafío y lenguajes
- Aunque se presenta como un desafío de Java, muchos participantes comentan y comparten implementaciones en C/C++, Rust, Go, .NET, Python, Perl, Awk, R, SQL, ClickHouse, DuckDB, Pinot y otros.
- Algunos desearían que otros lenguajes de JVM estuvieran permitidos oficialmente, mientras que otros señalan que la regla de “sin dependencias externas” excluye cosas como Hadoop o Pandas.
IO, caché y estrategias de acceso a archivos
- Un tema central es si la tarea está limitada por IO o por CPU.
- Como el archivo de 12 GB se ejecuta 5 veces en una máquina de 32 GB y las cachés no se vacían, las ejecuciones posteriores operan efectivamente desde la caché de páginas, lo que convierte el análisis y la agregación en el principal cuello de botella.
- Estrategias debatidas: leer en RAM frente a
mmap, IO con búfer frente aO_DIRECT,io_uring, búferes pequeños de tamaño fijo y cómo manejar líneas que cruzan límites de fragmentos. - Algunos argumentan que apoyarse en la política de caché del SO hace que el benchmark sea menos “puro”; otros dicen que refleja implementaciones reales.
Restricciones de corrección y preocupaciones por sobreajuste
- Los nombres de estación deben ser UTF-8 arbitrario (sin
;), de hasta 100 caracteres; las temperaturas van de −99.9 a 99.9 con exactamente un decimal. - Esto permite aritmética entera (escalando por 10) y un análisis más simple que el de punto flotante completo.
- Se descubrió que varias soluciones rápidas usaban incorrectamente funciones hash o dependían del conjunto de datos específico y fueron eliminadas; hay debate sobre qué cuenta como “sobreajuste” injusto.
- Surgen ideas de aleatorización del conjunto de datos o semillas ocultas para evitar ajustar una solución a un solo archivo.
Debate sobre la metodología del benchmark
- El organizador descarta la ejecución más rápida y la más lenta de 5 y promedia las 3 del medio.
- Unos sostienen que el tiempo mínimo refleja mejor el potencial algorítmico sin ruido.
- Otros responden que las medias recortadas aproximan mejor el rendimiento real bajo ruido del sistema, contención de máquinas virtuales y efectos del GC.
Estrategias algorítmicas y de implementación
- Sugerencias comunes: procesamiento en flujo con agregados por estación, temperaturas enteras, análisis numérico hecho a mano, SIMD para búsqueda de delimitadores y parsing, minimizar asignaciones y diseñar con cuidado la hash map.
- Algunos exploran ideas exóticas como máquinas de estados personalizadas, hash perfectos o analizadores SIMD especializados con estado, aunque se debate su viabilidad.
Uso de bases de datos y motores analíticos
- Varios participantes demuestran que motores SQL (PostgreSQL, ClickHouse, DuckDB, Pinot, kdb+/q) resuelven la consulta de forma muy concisa pero a menudo más lenta, especialmente al incluir la ingestión.
- Hay discusión sobre el crecimiento del almacenamiento, estrategias de índices, trucos con arrays, planes de consulta paralelos y COPY frente a INSERT para cargas masivas.
Java, herramientas de build y comparaciones de lenguajes
- Las opiniones sobre Java están divididas: algunos elogian su rendimiento y herramientas; otros se quejan de la verbosidad y la repetición.
- Largas subtramas comparan Maven, Gradle y las herramientas de Cargo/Go, el comportamiento de la compilación incremental y la percepción de lentitud en las compilaciones.
- Algunos señalan que, aunque los lenguajes de bajo nivel pueden ser los más rápidos, un Java ingenuo a menudo supera a un C/C++ ingenuo gracias a valores predeterminados más seguros y a las optimizaciones del JIT.