Halo, Habr!
Bekerja dengan Javascript / naskah, saya perhatikan untuk waktu yang lama bahwa API asinkron lebih lambat dari API sinkron yang sama, dan saya bahkan tahu bahwa seharusnya begitu. Namun dalam proyek terakhir, pekerjaan asinkron dengan sistem file menjadi hambatan, dan saya mengurus pengukuran.
Diketahui bahwa menunggu hanya dapat digunakan di dalam fungsi atau blok async , yang berarti bahwa jika kita memiliki tingkat API terendah asinkron, maka Anda harus menggunakan async / menunggu hampir di mana-mana, bahkan di tempat yang jelas tidak diperlukan.
Sebagai contoh, kami menulis fungsi layanan yang mengambil objek dari penyimpanan dengan kunci. Sebagai penyimpanan, kita dapat menggunakan file, basis data, layanan mikro, yaitu sumber lambat dengan antarmuka asinkron. Untuk meningkatkan kinerja, di dalam fungsi kami, kami cache objek yang sebelumnya diekstraksi (menambahkannya ke Peta ). Ketika program akses nyata ke toko menjadi lebih kecil, objek dikembalikan dari cache cepat, tetapi antarmuka fungsi tetap asinkron!
Berapa harga yang harus saya bayarkan untuk setiap panggilan asinkron?
Hasil tes menyedihkan ...
Ambil fungsi sederhana, tandai async , dan kami akan memanggilnya dalam satu lingkaran, mengukur total waktu, dan membandingkannya dengan kode sinkron yang serupa. Untuk membandingkan sintaks, saya kutip teks lengkap dalam 3 bahasa.
Naskah ( Deno )
Kode sinkronconst 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 }
Kode asinkron:
(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)
Kode sinkron 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; } }
Kode asinkron:
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; } }
Karat
Kode sinkron 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 }
Kode asinkron:
Hasil
Kita melihat bahwa aritmatika dalam ketiga bahasa sama baiknya, tetapi biaya overhead untuk menunggu berbeda dengan urutan besarnya. Menariknya, di mana penggunaan async / menunggu adalah yang paling umum (dan bahkan dipropagandakan), biaya panggilan asinkron hanyalah penghalang. Rust memenangkan lomba seperti biasa, mungkin ini adalah alasan utama mengapa kerangka kerja WEB yang tertulis di dalamnya secara konsisten memenangkan tolok ukur selama lebih dari setahun.
Ringkasan
Bukan tanpa alasan, pengembang Java tidak terburu-buru untuk menambahkan sintaks asinkron langsung ke bahasa, dan meskipun saya percaya bahwa async / menunggu adalah abstraksi yang hebat, kita harus memahami skala overhead ketika menggunakannya.
PS
Terima kasih kepada semua orang yang menunjukkan kemungkinan mempercepat kode dengan caching tugas / janji (alih-alih caching hasilnya), serta kehadiran di C # dari alat hebat yang hanya memecahkan masalah saya.