Hola Habr!
Al trabajar con Javascript / Typecript, noté durante mucho tiempo que la API asincrónica es más lenta que la API síncrona similar, e incluso supe que debería ser así. Pero en el último proyecto, el trabajo asincrónico con el sistema de archivos se convirtió en un cuello de botella, y me encargué de las mediciones.
Se sabe que wait solo puede usarse dentro de funciones o bloques asíncronos , lo que significa que si tenemos el nivel API más bajo asíncrono, entonces tendrá que usar async / wait en casi todas partes, incluso donde obviamente no es necesario.
Por ejemplo, escribimos una función de servicio que toma un objeto del almacenamiento por clave. Como almacenamiento, podemos usar un archivo, base de datos, microservicio, es decir, una fuente lenta con una interfaz asíncrona. Para mejorar el rendimiento, dentro de nuestra función, almacenamos en caché los objetos extraídos previamente (los agregamos al Mapa ). A medida que el programa de accesos reales a la tienda se vuelve más pequeño, los objetos se devuelven de la memoria caché rápida, ¡pero la interfaz de la función permanece asíncrona!
¿Qué precio debo pagar por cada llamada asincrónica?
Los resultados de las pruebas son deprimentes ...
Tome una función simple, márquela como asíncrona y la llamaremos en un bucle, midiendo el tiempo total y comparándolo con un código síncrono similar. Para comparar la sintaxis, cito los textos completos en 3 idiomas.
Mecanografiado ( Deno )
Código síncronoconst b = Date.now() let j = 0.0 for (let i = 0; i < 1_000_000_000; i++) { j += f(i) } console.log(j + ', ' + (Date.now() - b)/1000 + 's') function f(i: number): number { return i / 3.1415926 }
Código asincrónico:
(async () => { const b = Date.now() let j = 0.0 for (let i = 0; i < 1_000_000_000; i++) { j += await f(i) } console.log(j + ', ' + (Date.now() - b)/1000 + 's') })() async function f(i: number): Promise<number> { return i / 3.1415926 }
C # (.NET Core)
Código síncrono using System; class App { static void Main(string[] args) { var b = DateTime.Now; var j = 0.0; for (var i = 0L; i < 1_000_000_000L; i++) { j += f(i); } Console.WriteLine(j + ", " + (DateTime.Now - b).TotalMilliseconds / 1000 + "s"); } static double f(long i) { return i / 3.1415926; } }
Código asincrónico:
using System; using System.Threading.Tasks; class App { static async Task Main(string[] args) { var b = DateTime.Now; var j = 0.0; for (var i = 0L; i < 1_000_000_000L; i++) { j += await f(i); } Console.WriteLine(j + ", " + (DateTime.Now - b).TotalMilliseconds / 1000 + "s"); } static async Task<double> f(long i) { return i / 3.1415926; } }
Herrumbre
Código síncrono fn main() { let tbegin = std::time::SystemTime::now(); let mut j = 0.0; for i in 0..1_000_000_000i64 { j += f(i); } println!("{:?}, {:?}", j, tbegin.elapsed().unwrap()); } fn f(i: i64) -> f64 { return i as f64 / 3.1415926 }
Código asincrónico:
Resultados
Vemos que la aritmética en los 3 idiomas es igualmente buena, pero la sobrecarga de espera difiere en un orden de magnitud. Curiosamente, donde el uso de async / waitit es el más común (e incluso propagandizado), el costo de una llamada asincrónica es simplemente prohibitivo. Rust ganó la carrera como siempre, tal vez esta es la razón principal por la cual el marco WEB escrito en él ha ganado constantemente puntos de referencia durante más de un año.
Resumen
No sin razón, los desarrolladores de Java no tienen prisa por agregar una sintaxis asíncrona directamente al lenguaje, y aunque creo que async / wait es una gran abstracción, debemos entender la escala de la sobrecarga cuando lo usamos.
PS
Gracias a todos los que señalaron la posibilidad de acelerar el código mediante el almacenamiento en caché de tareas / promesas (en lugar de almacenar en caché los resultados), así como la presencia en C # de una gran herramienta que simplemente resuelve mi problema.