2024’te 1 milyon eşzamanlı iş çalıştırmak için gereken bellek miktarı
(hez2010.github.io)Benchmark
-
Coroutine nedir?
- Coroutine, bir programın yürütülmesini geçici olarak durdurup yeniden başlatabilen bir bilgisayar programı bileşenidir; işbirlikçi çoklu görev için alt yordamların genelleştirilmiş hâlidir.
- İşbirlikçi işler, istisnalar, event loop’lar, yineleyiciler, sonsuz listeler ve pipe gibi program bileşenlerini uygulamak için uygundur.
-
Rust
- İki program yazıldı:
tokioveasync_stdkullanan programlar. - Her ikisi de Rust’ta yaygın olarak kullanılan asenkron runtime’lardır.
- İki program yazıldı:
-
C#
- C#, Rust’a benzer şekilde
async/awaitdesteği sunar. - .NET 7’den itibaren NativeAOT derlemesi sunarak VM olmadan da yönetilen kodun çalıştırılmasını mümkün kılar.
- C#, Rust’a benzer şekilde
-
NodeJS
- Asenkron işler için
Promise.allkullanıldı.
- Asenkron işler için
-
Python
- Asenkron işleri yürütmek için
asynciomodülü kullanıldı.
- Asenkron işleri yürütmek için
-
Go
- Eşzamanlılık, goroutine’ler ile uygulanır ve işleri beklemek için
WaitGroupkullanılır.
- Eşzamanlılık, goroutine’ler ile uygulanır ve işleri beklemek için
-
Java
- JDK 21’den itibaren goroutine’lere benzer bir kavram olan sanal thread’ler sunuluyor.
- GraalVM kullanılarak native image üretilebiliyor.
Test ortamı
- Donanım: 13. nesil Intel(R) Core(TM) i7-13700K
- İşletim sistemi: 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
Sonuçlar
-
En düşük bellek kullanımı
- Rust, C# (NativeAOT) ve Go, native binary olarak derlendiği için az bellek kullanıyor.
- Java (GraalVM native image) da iyi performans gösterdi, ancak diğer statik derlenen dillere göre daha fazla bellek kullandı.
-
10 bin iş
- Rust’ta bellek kullanımı neredeyse hiç artmıyor.
- C# (NativeAOT) da az bellek kullanıyor.
- Go, beklenenden daha fazla bellek kullanıyor.
-
100 bin iş
- Rust ve C# iyi performans gösteriyor.
- C# (NativeAOT), Rust’tan daha az bellek kullanıyor.
-
1 milyon iş
- C#, tüm dilleri açık farkla geride bırakarak en az belleği kullanıyor.
- Rust da bellek verimliliğinde çok başarılı.
- Go, diğer dillere kıyasla daha fazla bellek kullanıyor.
Sonuç
- Çok sayıda eşzamanlı iş, karmaşık işlemler yapmasa bile önemli miktarda bellek tüketebilir.
- .NET ve NativeAOT’taki iyileştirmeler dikkat çekici; GraalVM ile derlenen Java native image da bellek verimliliğinde oldukça başarılı.
- Goroutine’ler kaynak tüketimi açısından hâlâ verimsiz.
Ek
- Rust (tokio) tarafında
join_allyerinefordöngüsü kullanılarak bellek kullanımı yarıya indirildi. Rust, bu benchmark’ta mutlak liderliği ele geçirdi.
1 yorum
Hacker News yorumları
Benchmark, Node ile Go'nun asenkron işleme biçimleri arasındaki farkı yeterince yansıtmıyor. Node
Promise.allkullanırken Go goroutine kullanıyor; bu yüzden fark oluşuyor. Asenkron I/O ile CPU-bound işlerin bellek kullanım farkını karşılaştırmak ilginç olurdu"10 saniye bekleyen iş" ile "10 saniye sonra uyandırılan iş" arasındaki fark açıklanıyor. Go kodunun bellek kullanımı diğer kodlarla karşılaştırıldığında oldukça farklı
Go ile Node'u adil biçimde karşılaştırmak için, zamanlayıcı planlayan bir goroutine ve zamanlayıcı sinyalini işleyen bir goroutine kullanma yöntemi öneriliyor. Node'a Bun ve Deno'nun dahil edilmemiş olması tuhaf bulunuyor
Çok sayıda eşzamanlı iş çok bellek tüketebilir, ancak iş başına veri birkaç KB'yi aşıyorsa zamanlayıcının bellek ek yükü göz ardı edilebilir düzeyde kalır
"Eşzamanlı iş" tanımına göre bellek kullanımı değişebilir. Verimli bir uygulamada 1M eşzamanlı iş için yaklaşık 200MB gerekir
Go'nun bellek kullanımında Java'nın iki kattan fazla gerisinde kaldığına dikkat çekiliyor ve benchmark'ın gerçek programları temsil etmediği söyleniyor
Basit kodla dilleri karşılaştırmanın geliştiriciler açısından adil olmayabileceği, gerçek işler eklenerek bellek kullanımı ve zamanlama farklarının ölçülmesi gerektiği öneriliyor
Benchmark'ların sık sık hatalarla dolu olduğu ve bu tür benchmark'ları yayımlayan insanların motivasyonunun anlaşılamadığı söyleniyor
Java benchmark'ının hatalı olabileceği,
ArrayListiçin başlangıç boyutu belirtilmediğinden gereksiz yere çok sayıda nesne oluşturulduğu belirtiliyorRust'un asenkron kodunun neden beklenenden daha hızlı tamamlandığı açıklanıyor.
tokio::time::sleep()geleceğin oluşturulduğu zamanı takip ettiği için böyle oluyor