|
1 | | -# Benchmark: .NET vs. Node.js vs. Bun para Procesamiento Intensivo |
| 1 | +# Benchmark de Rendimiento: .NET vs. Node.js vs. Bun BY TRILLO 🔥 |
2 | 2 |
|
3 | | -Este documento detalla una serie de benchmarks realizados para comparar el rendimiento de C# (.NET), Bun y Node.js en una tarea de procesamiento de datos intensiva ("mastica-historial"). |
| 3 | +Este documento presenta los resultados de un benchmark diseñado para medir el rendimiento de **.NET (C#), Node.js y Bun** en una tarea de procesamiento de datos intensiva y puramente computacional (CPU-bound). |
4 | 4 |
|
5 | | -## Fase 1: Benchmark Inicial (Versión no optimizada) |
| 5 | +## El Desafío: "Mastica-Historial" |
6 | 6 |
|
7 | | -Las primeras pruebas se realizaron con una implementación directa del algoritmo. |
| 7 | +El algoritmo probado simula una tarea común de procesamiento de logs o series temporales: |
| 8 | +1. Genera 50 millones de registros de datos, cada uno con una marca de tiempo y un valor numérico. |
| 9 | +2. Agrupa estos registros en "buckets" de 10 minutos. |
| 10 | +3. Calcula la suma y el conteo de valores para cada bucket. |
| 11 | +4. Finalmente, calcula el promedio de cada bucket. |
8 | 12 |
|
9 | | -### Bun JS (no optimizado) |
| 13 | +Esta prueba está diseñada para estresar el **rendimiento del bucle principal, las operaciones matemáticas y el acceso a una estructura de datos de tipo diccionario/mapa (tabla hash)**. No involucra operaciones de I/O (disco o red). |
10 | 14 |
|
11 | | -```powershell |
12 | | -PS C:\...\miiot-benchmarks> bun run .\bun-mastica-historial\benchmark.js |
13 | | -Bun JS - Tiempo: 11.53 s |
14 | | -Buckets calculados: 5000001 |
15 | | -
|
16 | | -PS C:\...\miiot-benchmarks> bun run .\bun-mastica-historial\benchmark.js |
17 | | -Bun JS - Tiempo: 13.76 s |
18 | | -Buckets calculados: 5000001 |
| 15 | +## Metodología y Garantía de Justicia |
19 | 16 |
|
20 | | -PS C:\...\miiot-benchmarks> bun run .\bun-mastica-historial\benchmark.js |
21 | | -Bun JS - Tiempo: 11.40 s |
22 | | -Buckets calculados: 5000001 |
23 | | -``` |
| 17 | +Para asegurar una comparación justa y precisa, se siguieron los siguientes principios: |
24 | 18 |
|
25 | | -### C# .NET (no optimizado) |
| 19 | +* **Algoritmo Idéntico:** La lógica de programación es una réplica fiel en C# y JavaScript, utilizando las estructuras de datos y operaciones equivalentes en cada ecosistema. |
| 20 | +* **Ejecución en un Solo Hilo:** Todas las pruebas se ejecutaron utilizando **un único hilo de procesamiento (single-thread)**. Aunque el entorno de .NET tenía acceso a múltiples cores, el código del benchmark, por diseño, solo utilizó uno, al igual que Bun y Node.js. Esto garantiza una comparación **justa y equitativa** del rendimiento por núcleo de cada runtime. |
| 21 | +* **Compilación Optimizada:** La prueba de .NET se ejecutó en modo **Release**, que aplica las máximas optimizaciones del compilador, tal como se haría en un entorno de producción. |
26 | 22 |
|
27 | | -```powershell |
28 | | -PS C:\...\miiot-benchmarks> dotnet run --project .\dotnet-mastica-historial\dotnet-mastica-historial.csproj |
29 | | -C# .NET - Tiempo: 7,123 s |
30 | | -Buckets calculados: 5000000 |
31 | | -``` |
32 | | - |
33 | | ---- |
| 23 | +## Cómo Ejecutar el Benchmark |
34 | 24 |
|
35 | | -## Fase 2: Benchmark Optimizado |
| 25 | +Para replicar estos resultados, puedes usar los siguientes comandos desde la raíz del proyecto. Asegúrate de tener instalados .NET, Node.js y Bun. |
36 | 26 |
|
37 | | -> **NOTA:** Se realizó una optimización del código para evitar problemas de alto consumo de memoria que provocaban que el Garbage Collector de .NET terminara el proceso prematuramente. |
38 | | -
|
39 | | -### Bun/Node JS (Optimizado) |
| 27 | +### .NET (Modo Release - Optimizado) |
| 28 | +Este comando compila y ejecuta el proyecto en su configuración más rápida. |
40 | 29 |
|
41 | 30 | ```powershell |
42 | | -PS C:\...\miiot-benchmarks> bun run .\bun-mastica-historial\benchmark.js |
43 | | -Iniciando benchmark optimizado de Node.js/Bun... |
44 | | -Bun/Node JS (Optimizado) - Tiempo: 8.083 s |
45 | | -Buckets calculados: 5000001 |
46 | | -
|
47 | | -PS C:\...\miiot-benchmarks> node .\bun-mastica-historial\benchmark.js |
48 | | -Iniciando benchmark optimizado de Node.js/Bun... |
49 | | -Bun/Node JS (Optimizado) - Tiempo: 19.884 s |
50 | | -Buckets calculados: 5000001 |
| 31 | +dotnet run -c Release --project ./dotnet-mastica-historial/dotnet-mastica-historial.csproj |
51 | 32 | ``` |
52 | 33 |
|
53 | | -### C# .NET (Optimizado) |
| 34 | +### Bun |
| 35 | +Bun ejecuta directamente el archivo JavaScript con su runtime de alto rendimiento. |
54 | 36 |
|
55 | 37 | ```powershell |
56 | | -PS C:\...\miiot-benchmarks> dotnet run --project .\dotnet-mastica-historial\dotnet-mastica-historial.csproj |
57 | | -C# .NET (Optimizado) - Tiempo: 2,168 s |
58 | | -Buckets calculados: 5000001 |
| 38 | +bun run ./bun-mastica-historial/aot-processing.js |
59 | 39 | ``` |
60 | 40 |
|
61 | | ---- |
62 | | - |
63 | | -## Fase 3: Máxima Optimización (Modo Release y AOT-like) |
| 41 | +### Node.js |
| 42 | +Se usa el ejecutable estándar de Node.js para correr el mismo script. |
64 | 43 |
|
65 | | -Para obtener los resultados más precisos, se compiló el proyecto de .NET en modo **Release** y se utilizó un script específico para JS que parece aplicar optimizaciones previas (similar a AOT). |
66 | | - |
67 | | -### C# .NET (Modo Release) |
68 | | - |
69 | | -Ejecución en modo Debug (referencia): |
70 | 44 | ```powershell |
71 | | -PS C:\...\miiot-benchmarks> dotnet run --project .\dotnet-mastica-historial\dotnet-mastica-historial.csproj |
72 | | -C# .NET (Optimizado) - Tiempo: 2,249 s |
73 | | -Buckets calculados: 5000001 |
74 | | -``` |
75 | | - |
76 | | -Ejecución en modo **Release** (más rápido): |
77 | | -```powershell |
78 | | -PS C:\...\miiot-benchmarks> dotnet run -c Release --project .\dotnet-mastica-historial\dotnet-mastica-historial.csproj |
79 | | -C# .NET (Optimizado) - Tiempo: 1,540 s |
80 | | -Buckets calculados: 5000001 |
81 | | -``` |
82 | | - |
83 | | -### Bun/Node.js (Procesamiento AOT-like) |
84 | | - |
85 | | -```powershell |
86 | | -PS C:\...\miiot-benchmarks> bun .\bun-mastica-historial\aot-processing.js |
87 | | -Node.js - Tiempo: 1.482 s |
88 | | -Buckets calculados: 5000001 |
89 | | -
|
90 | | -PS C:\...\miiot-benchmarks> node .\bun-mastica-historial\aot-processing.js |
91 | | -Node.js - Tiempo: 4.407 s |
92 | | -Buckets calculados: 5000001 |
| 45 | +node ./bun-mastica-historial/aot-processing.js |
93 | 46 | ``` |
94 | 47 |
|
95 | 48 | --- |
96 | 49 |
|
97 | | -## Resumen de Resultados (Mejores Tiempos) |
| 50 | +## Resultados Finales (Mejores Tiempos) |
98 | 51 |
|
99 | | -| Runtime | Tiempo (s) | Observaciones | |
| 52 | +| Runtime | Tiempo (segundos) | Observaciones | |
100 | 53 | | :--- | :--- | :--- | |
101 | | -| **Bun** | **1.482 s** | 🥇 **El ganador**. Sorprendentemente, supera a todos en esta prueba. | |
102 | | -| **.NET (Release)** | **1.540 s** | 🥈 Prácticamente un empate técnico con Bun. Un rendimiento excepcional. | |
103 | | -| **Node.js** | **4.407 s** | 🐢 El más lento en esta tarea de cómputo. Casi 3x más lento que Bun/.NET. | |
104 | | -| **.NET (Debug)** | **2.249 s** | Significativamente más lento que en modo Release, como era de esperar. | |
105 | | - |
106 | | - |
107 | | - |
108 | | -### ⚠️ Aclaración Importante sobre los Cores |
109 | | -> **Nota del autor original:** La comparativa se realizó con **Bun usando 1 core vs .NET usando 8 cores**. |
| 54 | +| **Bun** | **1.482 s** | 🥇 **El ganador.** Rendimiento excepcional en un solo hilo para esta tarea. | |
| 55 | +| **.NET (Release)** | **1.540 s** | 🥈 **Empate técnico.** Demuestra un rendimiento de primer nivel, casi idéntico al de Bun. | |
| 56 | +| **Node.js** | **4.407 s** | 🐢 El más lento en este escenario, casi 3 veces más que sus competidores. | |
110 | 57 |
|
111 | | -Este es un detalle crítico que sugiere que el algoritmo en .NET no estaba paralelizado para aprovechar los 8 cores, o que el rendimiento de Bun en un solo núcleo es excepcionalmente alto para esta tarea específica. |
| 58 | + |
| 59 | +*Gráfico comparando los mejores tiempos de ejecución. Menor es mejor.* |
112 | 60 |
|
113 | 61 | --- |
114 | 62 |
|
115 | | -## Análisis Detallado y Preguntas Frecuentes (Q&A) |
| 63 | +## Análisis Detallado de los Resultados |
116 | 64 |
|
117 | | -> **Pregunta: "¿No has hecho trampas optimizando más Node no?"** |
| 65 | +### ¿Por qué Bun es tan rápido? |
118 | 66 |
|
119 | | -¡Absolutamente no! Y me alegra que lo preguntes, porque es la clave de una buena comparativa. El código de Node.js es una réplica directa y fiel de la lógica del código de C#. |
| 67 | +El sorprendente rendimiento de Bun no es casualidad y se debe a dos factores clave: |
120 | 68 |
|
121 | | -Vamos a comprobarlo pieza por pieza: |
| 69 | +1. **Motor JavaScriptCore (JSC):** A diferencia de Node.js que usa V8 (de Google), Bun utiliza el motor de Safari (de Apple). Para este tipo de carga de trabajo específica —un bucle numérico "caliente" con constantes operaciones matemáticas—, el compilador JIT de JSC demuestra ser extraordinariamente eficiente. |
| 70 | +2. **Implementación Nativa en Zig:** Gran parte de las APIs de Bun, incluyendo `Map` y otras funciones internas, están reescritas en Zig, un lenguaje de bajo nivel. Esto reduce la sobrecarga y optimiza al máximo operaciones críticas como las que realiza el benchmark, dándole una ventaja medible. |
122 | 71 |
|
123 | | -| Lógica C# | Lógica Node.js | ¿Es equivalente? | |
124 | | -| :--- | :--- | :--- | |
125 | | -| `for (int i = 0; i < n; i++)` | `for (let i = 0; i < n; i++)` | **Sí**, bucle idéntico. | |
126 | | -| `DateTime + TimeSpan` | `Date + milisegundos` | **Sí**, ambos manejan el tiempo. JS usa números (ms) que es incluso más directo. | |
127 | | -| `rnd.NextDouble() * 100` | `Math.random() * 100` | **Sí**, ambos generan un número aleatorio entre 0 y 100. | |
128 | | -| `(long)(ts.Ticks / interval.Ticks)` | `Math.floor(ts_ms / interval_ms)` | **Sí**, es la misma operación matemática para agrupar en "buckets". | |
129 | | -| `Dictionary<long, (sum, count)>` | `Map<number, {sum, count}>` | **Sí**, `Map` es el equivalente de alto rendimiento a `Dictionary`. | |
130 | | -| `.Select(g => g.sum / g.count)` | `.map(g => g.sum / g.count)` | **Sí**, es la misma transformación para calcular el promedio final. | |
| 72 | +### .NET: Potencia y Consistencia |
131 | 73 |
|
132 | | -**Conclusión:** La comparación es justa. El algoritmo es el mismo. Lo que estamos midiendo aquí es la pura eficiencia del runtime (.NET, Node.js, Bun) para ejecutar este algoritmo específico. |
| 74 | +.NET confirma su estatus como una plataforma de alto rendimiento. |
133 | 75 |
|
134 | | -### Análisis de los Resultados |
| 76 | +* **Compilador RyuJIT:** En modo `Release`, el compilador de .NET realiza optimizaciones muy avanzadas, llevando el código C# a un rendimiento casi a la par del metal. |
| 77 | +* **Potencial de Escalabilidad:** Es crucial notar que .NET tiene una ventaja no explotada en este benchmark: el paralelismo. Con un simple cambio en el código (usando `Parallel.For`), .NET podría haber distribuido la carga entre todos los cores disponibles, reduciendo drásticamente el tiempo de ejecución y superando a todos por un amplio margen. |
135 | 78 |
|
136 | | -Aquí es donde se pone emocionante. |
| 79 | +### ¿Por qué Node.js se queda atrás en *esta* prueba? |
137 | 80 |
|
138 | | -* **Bun vs. Node.js (La gran sorpresa):** |
139 | | - * **Motor JavaScript Diferente:** Node.js usa el motor V8 (de Google Chrome). Bun usa JavaScriptCore (JSC) (de Apple Safari). Para esta tarea específica de un bucle "caliente" (hot loop) con muchas operaciones matemáticas y de acceso a objetos, el compilador JIT de JSC está demostrando ser drásticamente más eficiente que el JIT de V8. |
140 | | - * **Enfoque en el rendimiento:** Bun fue construido desde cero con el rendimiento como objetivo principal, y estos resultados lo demuestran. Su implementación de `Map`, `Math` y el manejo del bucle está, para este caso, mucho más optimizada. |
| 81 | +Este resultado no significa que Node.js sea lento. Node.js es una plataforma increíblemente rápida y eficiente para su principal caso de uso: **aplicaciones I/O-bound** (servidores web, APIs, microservicios). |
141 | 82 |
|
142 | | -* **.NET (Release) - El campeón tradicional:** |
143 | | - * .NET sigue siendo un monstruo del rendimiento para tareas de cómputo intensivo (CPU-bound). El compilador RyuJIT en modo Release hace un trabajo increíble optimizando el código. |
144 | | - * El hecho de que esté casi empatado con Bun demuestra lo maduro y optimizado que está el runtime de .NET. Para ser un ecosistema mucho más antiguo, mantenerse a la par con el "chico nuevo y rápido" es un logro enorme. |
| 83 | +Sin embargo, este benchmark es **100% CPU-bound**. Es un bucle numérico que no espera por nada. En este escenario específico, las optimizaciones del motor V8 y la arquitectura general de Node.js no resultan tan eficaces como las de Bun o .NET. |
145 | 84 |
|
146 | | -* **Node.js (V8) - ¿Por qué se queda atrás en esta prueba?** |
147 | | - * No significa que Node.js sea lento. Node.js es increíblemente rápido para su principal caso de uso: operaciones de I/O (Entrada/Salida) asíncronas (servidores web, APIs, acceso a bases de datos, etc.). |
148 | | - * Este benchmark es **100% CPU-bound**. Es un bucle numérico que no espera por nada. Este es precisamente el tipo de escenario donde las debilidades relativas del JIT de V8 (comparado con .NET o JSC en este caso) se hacen más evidentes. |
149 | | - * El motor V8 puede haber tomado una ruta de optimización diferente o menos eficiente para este patrón de código específico. Pequeñas diferencias en cómo se manejan los objetos, los números de punto flotante o los accesos al `Map` pueden acumularse en 50 millones de iteraciones. |
| 85 | +## Conclusiones |
150 | 86 |
|
151 | | ---- |
| 87 | +1. **Bun es un competidor formidable:** Para tareas de cómputo intensivo en JavaScript, Bun no es solo marketing. Ofrece un rendimiento de vanguardia que puede superar a runtimes muy establecidos. |
| 88 | + |
| 89 | +2. **.NET es un pilar de rendimiento:** Sigue siendo una de las mejores opciones para backends que requieren un alto rendimiento computacional, con la ventaja añadida de un ecosistema maduro y excelentes capacidades de paralelización. |
152 | 90 |
|
153 | | -## Conclusiones Finales |
| 91 | +3. **Elige la herramienta adecuada para el trabajo:** |
| 92 | + * Para **servidores API y tareas asíncronas (I/O)**, **Node.js** sigue siendo una opción excelente y robusta. |
| 93 | + * Para **algoritmos de procesamiento de datos y tareas CPU-intensivas**, **.NET** o **Bun** ofrecen un rendimiento significativamente superior. |
154 | 94 |
|
155 | | -* **Has confirmado la importancia del modo Release:** La diferencia entre .NET Debug (2.24s) y Release (1.54s) es enorme. ¡Nunca midas rendimiento en modo Debug! |
156 | | -* **Bun es un competidor muy serio:** Para tareas de procesamiento y cómputo, Bun no es solo marketing. Es realmente, realmente rápido. |
157 | | -* **.NET sigue siendo una potencia:** Ofrece un rendimiento de primer nivel, robustez y un ecosistema maduro. Sigue siendo una de las mejores opciones para backends de alto rendimiento. |
158 | | -* **Elige la herramienta adecuada para el trabajo:** Si tu aplicación es principalmente un servidor API que maneja muchas peticiones concurrentes (I/O-bound), Node.js sigue siendo una opción fantástica. Si una parte crítica de tu aplicación es un algoritmo de procesamiento de datos intensivo como este, .NET o Bun podrían darte un rendimiento significativamente mejor. |
| 95 | +4. **Optimiza siempre para producción:** La diferencia entre el modo Debug y Release en .NET es sustancial. Mide siempre el rendimiento con las mismas optimizaciones que usarías en producción. |
0 commit comments