Kapasitas memori yang dibutuhkan untuk menjalankan 1 juta tugas konkuren pada tahun 2024
(hez2010.github.io)Benchmark
-
Apa itu coroutine?
- Coroutine adalah komponen program komputer yang dapat menangguhkan dan melanjutkan eksekusi program, yaitu generalisasi dari subrutin untuk multitasking kooperatif.
- Cocok untuk mengimplementasikan komponen program seperti pekerjaan kooperatif, pengecualian, event loop, iterator, daftar tak terbatas, dan pipe.
-
Rust
- Ditulis dua program: program yang menggunakan
tokiodanasync_std. - Keduanya adalah runtime asinkron yang umum digunakan di Rust.
- Ditulis dua program: program yang menggunakan
-
C#
- C# mendukung
async/await, mirip dengan Rust. - Sejak .NET 7, tersedia kompilasi NativeAOT sehingga kode terkelola dapat dijalankan tanpa VM.
- C# mendukung
-
NodeJS
- Menggunakan
Promise.alluntuk tugas asinkron.
- Menggunakan
-
Python
- Menggunakan modul
asynciountuk menjalankan tugas asinkron.
- Menggunakan modul
-
Go
- Mengimplementasikan konkurensi menggunakan goroutine, dan menunggu tugas dengan
WaitGroup.
- Mengimplementasikan konkurensi menggunakan goroutine, dan menunggu tugas dengan
-
Java
- Sejak JDK 21, tersedia virtual thread, yang merupakan konsep serupa dengan goroutine.
- Dapat membuat native image menggunakan GraalVM.
Lingkungan pengujian
- Perangkat keras: Intel(R) Core(TM) i7-13700K generasi ke-13
- Sistem operasi: Debian GNU/Linux 12 (bookworm)
- Rust: 1.82.0
- .NET: 9.0.100
- Go: 1.23.3
- Java: openjdk 23.0.1
- Java (GraalVM): java 23.0.1
- NodeJS: v23.2.0
- Python: 3.13.0
Hasil
-
Penggunaan memori minimum
- Rust, C# (NativeAOT), dan Go dikompilasi menjadi biner native sehingga menggunakan memori yang kecil.
- Java (native image GraalVM) juga menunjukkan performa yang baik, tetapi tetap menggunakan lebih banyak memori dibanding bahasa lain yang dikompilasi secara statis.
-
10 ribu tugas
- Penggunaan memori Rust hampir tidak meningkat.
- C# (NativeAOT) juga menggunakan sedikit memori.
- Go menggunakan lebih banyak memori dari yang diperkirakan.
-
100 ribu tugas
- Rust dan C# menunjukkan performa yang baik.
- C# (NativeAOT) menggunakan lebih sedikit memori dibanding Rust.
-
1 juta tugas
- C# mengungguli semua bahasa lain dan menggunakan memori paling sedikit.
- Rust juga sangat efisien dalam penggunaan memori.
- Go menggunakan memori lebih banyak dibanding bahasa lain.
Kesimpulan
- Banyak tugas konkuren dapat mengonsumsi memori dalam jumlah besar meskipun tidak menjalankan pekerjaan yang kompleks.
- Peningkatan pada .NET dan NativeAOT sangat menonjol, dan native image Java yang dibangun dengan GraalVM juga sangat efisien dalam penggunaan memori.
- Goroutine masih tidak efisien dari sisi konsumsi sumber daya.
Lampiran
- Di Rust (
tokio), penggunaan memori dikurangi setengah dengan memakai loopforalih-alihjoin_all. Rust menjadi pemimpin mutlak dalam benchmark ini.
1 komentar
Opini Hacker News
Benchmark tersebut tidak secara tepat mencerminkan perbedaan cara Node dan Go menangani pemrosesan asinkron. Node menggunakan
Promise.allsedangkan Go menggunakan goroutine, sehingga ada perbedaan. Akan menarik untuk membandingkan perbedaan penggunaan memori antara I/O asinkron dan tugas yang CPU-boundMenjelaskan perbedaan antara "tugas yang menunggu selama 10 detik" dan "tugas yang dibangunkan setelah 10 detik". Penggunaan memori pada kode Go jauh berbeda dibandingkan kode lainnya
Mengusulkan metode yang menggunakan goroutine untuk menjadwalkan timer dan goroutine lain untuk menangani sinyal timer agar perbandingan antara Go dan Node lebih adil. Juga disebutkan bahwa aneh Bun dan Deno tidak disertakan dalam Node
Banyak tugas serentak memang dapat menghabiskan banyak memori, tetapi jika data per tugas lebih dari beberapa KB, overhead memori scheduler menjadi cukup kecil untuk diabaikan
Penggunaan memori bisa berbeda tergantung definisi dari "tugas serentak". Dalam implementasi yang efisien, sekitar 200MB dibutuhkan untuk 1M tugas serentak
Menunjukkan bahwa Go tertinggal lebih dari 2x dibanding Java dalam penggunaan memori, dan menyebut benchmark tersebut tidak mewakili program nyata
Membandingkan bahasa dengan kode sederhana bisa tidak adil bagi pengembang, dan disarankan menambahkan pekerjaan nyata untuk mengukur penggunaan memori serta perbedaan penjadwalan
Mengatakan bahwa benchmark sering kali penuh kesalahan, dan ia tidak memahami motivasi orang-orang yang memublikasikan benchmark semacam ini
Benchmark Java kemungkinan salah, karena ukuran awal
ArrayListtidak ditentukan sehingga banyak objek tidak perlu dibuatMenjelaskan mengapa kode async Rust selesai lebih cepat dari perkiraan. Itu karena
tokio::time::sleep()melacak titik waktu saat future dibuat