Welchen Preis zahlen wir für die Verwendung von async / await in JS / C # / Rust?

Hallo habr


Bei der Arbeit mit Javascript / Typescript ist mir lange aufgefallen, dass die asynchrone API langsamer ist als die ähnliche synchrone API, und ich wusste sogar, dass es so sein sollte. Im letzten Projekt wurde die asynchrone Arbeit mit dem Dateisystem zu einem Engpass, und ich kümmerte mich um die Messungen.


Es ist bekannt, dass await nur innerhalb von asynchronen Funktionen oder Blöcken verwendet werden kann. Wenn wir also die niedrigste asynchrone API-Ebene haben, müssen Sie async / await fast überall verwenden, auch dort, wo es offensichtlich nicht benötigt wird.


Zum Beispiel schreiben wir eine Servicefunktion, die ein Objekt per Schlüssel aus dem Speicher entnimmt. Als Speicher können wir eine Datei, eine Datenbank oder einen Mikrodienst verwenden, d. H. Eine langsame Quelle mit einer asynchronen Schnittstelle. Um die Leistung zu verbessern, werden in unserer Funktion zuvor extrahierte Objekte zwischengespeichert (zur Karte hinzugefügt). Wenn das Programm der realen Zugriffe auf den Speicher kleiner wird, werden Objekte aus dem schnellen Cache zurückgegeben, die Funktionsschnittstelle bleibt jedoch asynchron!


Welchen Preis muss ich für jeden asynchronen Anruf zahlen?
Testergebnisse sind deprimierend ...


Nehmen Sie eine einfache Funktion, markieren Sie sie als synchron, und wir rufen sie in einer Schleife auf, messen die Gesamtzeit und vergleichen sie mit einem ähnlichen synchronen Code. Um die Syntax zu vergleichen, zitiere ich die Volltexte in 3 Sprachen.


Typoskript ( Deno )


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

Asynchroner Code:


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


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

Asynchroner Code:


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

Rust


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

Asynchroner Code:


 //[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 } 

Ergebnisse


SpracheSynchroncode (Sek.)Asynchroner Code (Sek.)%% Verlust
Maschinenschrift7,4817323 mal
C #7.4676,210 mal
Rust7.4519.22,6 mal

Wir sehen, dass die Arithmetik in allen drei Sprachen gleich gut ist, aber der Overhead für das Warten unterscheidet sich um eine Größenordnung. Interessanterweise sind die Kosten eines asynchronen Anrufs einfach unerschwinglich, wenn die Verwendung von async / await am häufigsten vorkommt (und sogar propagiert wird). Rust hat das Rennen wie immer gewonnen, vielleicht ist dies der Hauptgrund, warum das darauf geschriebene WEB-Framework seit mehr als einem Jahr kontinuierlich Benchmarks gewinnt.


Zusammenfassung


Es ist kein Zufall, dass Java-Entwickler es nicht eilig haben, der Sprache direkt asynchrone Syntax hinzuzufügen, und obwohl ich der Meinung bin, dass async / await eine großartige Abstraktion ist, müssen wir das Ausmaß des Overheads verstehen, wenn wir es verwenden.


PS
Vielen Dank an alle, die auf die Möglichkeit hingewiesen haben, den Code durch Zwischenspeichern von Aufgaben / Versprechungen zu beschleunigen (anstatt die Ergebnisse zwischenzuspeichern), sowie das Vorhandensein eines großartigen Tools in C #, mit dem sich mein Problem einfach lösen lässt.

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


All Articles