Quel prix payons-nous pour utiliser async / attendre dans JS / C # / Rust

Bonjour, Habr!


En travaillant avec Javascript / Typescript, j'ai remarqué pendant longtemps que l'API asynchrone est plus lente que l'API synchrone similaire, et je savais même que cela devait être le cas. Mais dans le dernier projet, le travail asynchrone avec le système de fichiers est devenu un goulot d'étranglement, et j'ai pris soin des mesures.


Il est connu qu'attend ne peut être utilisé qu'à l'intérieur de fonctions ou de blocs asynchrones , ce qui signifie que si nous avons le niveau d'API asynchrone le plus bas, vous devrez utiliser async / wait presque partout, même là où il n'est évidemment pas nécessaire.


Par exemple, nous écrivons une fonction de service qui prend un objet du stockage par clé. Comme stockage, nous pouvons utiliser un fichier, une base de données, un microservice, c'est-à-dire une source lente avec une interface asynchrone. Pour améliorer les performances - à l'intérieur de notre fonction, nous mettons en cache les objets précédemment extraits (ajoutez-les à Map ). Au fur et à mesure que le programme d'accès réels au magasin devient plus petit, les objets sont renvoyés par le cache rapide, mais l'interface de fonction reste asynchrone!


Quel prix dois-je payer pour chaque appel asynchrone?
Les résultats des tests sont déprimants ...


Prenez une fonction simple, marquez-la comme async et nous l'appellerons en boucle, mesurant le temps total et la comparant avec un code synchrone similaire. Pour comparer la syntaxe, je cite les textes complets en 3 langues.


Typographie ( Deno )


Code synchrone
const 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 } 

Code asynchrone:


 (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)


Code synchrone
 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; } } 

Code asynchrone:


 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; } } 

Rouille


Code synchrone
 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 } 

Code asynchrone:


 //[dependencies] //futures = "0.3" use futures::executor::block_on; fn main() { block_on(async { let tbegin = std::time::SystemTime::now(); let mut j = 0.0; for i in 0..1_000_000_000i64 { j += f(i).await; } println!("{:?}, {:?}", j, tbegin.elapsed().unwrap()); }); } async fn f(i: i64) -> f64 { return i as f64 / 3.1415926 } 

Résultats


La langueCode synchrone (sec.)Code asynchrone (sec.)%% de perte
Typographie7.4817323 fois
C #7.4676,210 fois
Rouille7.4519,22,6 fois

Nous voyons que l'arithmétique dans les 3 langues est également bonne, mais le temps d' attente pour l' attente diffère d'un ordre de grandeur. Fait intéressant, lorsque l'utilisation de l' async / wait est la plus courante (et même propagée), le coût d'un appel asynchrone est tout simplement prohibitif. Rust a remporté la course comme toujours, c'est peut-être la principale raison pour laquelle le cadre WEB écrit dessus a constamment gagné des repères pendant plus d'un an.


Résumé


Non sans raison, les développeurs Java ne sont pas pressés d' ajouter directement la syntaxe asynchrone au langage, et bien que je pense que async / wait est une excellente abstraction, nous devons comprendre l'échelle de la surcharge lors de son utilisation.


PS
Merci à tous ceux qui ont souligné la possibilité d'accélérer le code en mettant en cache les tâches / promesses (au lieu de mettre en cache les résultats), ainsi que la présence en C # d'un excellent outil qui résout juste mon problème.

Source: https://habr.com/ru/post/fr483734/


All Articles