- Rust, Go, Java, C#, Python, Node.js, Elixir भाषाओं में async और multithread के बीच मेमोरी उपयोग की तुलना
- 10 सेकंड तक प्रतीक्षा करने वाले N tasks चलाने वाला प्रोग्राम हर भाषा में लिखा गया (ChatGPT की मदद से)
- Xeon E3 + Ubuntu 22.04 पर तुलना
परिणाम
- न्यूनतम footprint (सिर्फ 1 task का परीक्षण): Go और Rust को 3MB से कम चाहिए, Python को 17MB, Java/Node.js को लगभग 40MB, C# को 131MB
- 10 हजार tasks: Rust Tokio 4.6MB, Rust async-std 8MB, Go 28.6MB, Python 40MB, Rust Threads 48MB, Node.js 48MB, Java Virtual Thread 78MB, Elixir 99MB, C# 131MB, Java Threads 244MB
- 1 लाख tasks (threads को छोड़कर): Rust tokio 23MB, Rust Async-std 54MB, Node.js 112MB, C# 130MB, Java virtual threads 223 MB, Python 240MB, Go 269MB, Elixir 445MB
- 10 लाख tasks: Rust Tokio 213MB, C# 461MB, Node.js 494MB, Rust async-std 527MB, Java virtual thread 1154MB, Python 2232MB, Go 2658MB, Elixir 4009MB
निष्कर्ष
- Rust tokio का कोई मुकाबला नहीं
- C# का footprint बड़ा है, लेकिन यह बहुत प्रतिस्पर्धी है (कई बार Rust को भी पीछे छोड़ता है)
- Go में 10 लाख tasks पर Java virtual threads के साथ अंतर बढ़ जाता है (यह उस आम धारणा को उलट देता है कि Go, JVM से हल्का है)
- इसमें सिर्फ मेमोरी उपयोग देखा गया है, दूसरे कारकों पर विचार नहीं किया गया
- 10 लाख tasks पर काम शुरू करने का overhead बढ़ जाता है, और ज्यादातर कोड को पूरा होने में 12 सेकंड से ज्यादा लगते हैं
- आगे और benchmarks चलाने की योजना है
9 टिप्पणियां
Go का इस्तेमाल करते हुए और Rust को लगातार देखते-परखते हुए, यह काफ़ी मायने रखने वाला benchmark लगता है जब आप सोचते हैं कि क्या इस कड़े syntax के साथ खुद को ढालने की ज़रूरत है। अगर ऐसी स्थिति में भी जहाँ Go OOM की वजह से क्रैश हो जाए, Rust अच्छी तरह टिक जाए.... तो उसमें निवेश करना पूरी तरह वाजिब लगेगा.
बेशक, Rust developers को ढूँढना कहीं ज़्यादा मुश्किल होना अब भी एक समस्या रहेगी...
Go में हर अलग goroutine के लिए एक stack (2KB) अलग से allocate होता है, इसलिए usage O(n) जितना बढ़ता है और thread की संख्या बढ़ने पर यह नुकसानदेह होना सच है....
छोटी-सी जिज्ञासा यह है कि 10,000 threads से ऊपर जाने वाली स्थिति आखिर कितनी बार आती होगी। ऐसा लगता है कि असली code के चलने से ज़्यादा context switching ही बार-बार होगा....
मुझे Kotlin coroutines के बारे में भी जिज्ञासा है।
Elixir का नतीजा सबसे ज़्यादा चौंकाने वाला है; मुझे पता था कि Erlang, Go से भी हल्का है और सिर्फ़ कुछ सौ words स्तर की मेमोरी ही खाता है...
Erlang आधिकारिक दस्तावेज़ देखने पर पता चलता है कि एक Erlang process को spawn करने के लिए 338 words की ज़रूरत होती है। और 64-bit सिस्टम में 1 word 8 bytes का होता है, तो एक Erlang process लगभग 2.7KB (338 × 8 = 2,704) memory लेगा। Go भाषा में एक goroutine stack का आकार लगभग 2.0KB बताया गया है, इसलिए लगता है कि Erlang की तरफ़ memory usage ज़्यादा है.
तो साधारण गणना के अनुसार 10 लाख Erlang processes को 2.7GB memory लेनी चाहिए, लेकिन ऊपर बताए गए Elixir benchmark में लगभग 4.0GB की peak memory usage देखी गई, यानी 1.3GB memory अतिरिक्त इस्तेमाल हुई। सीधी गणना करें तो इसका मतलब है कि इस scenario में प्रति Erlang process लगभग 1.3KB memory और इस्तेमाल हुई। पक्का तो नहीं कह सकता, लेकिन ऐसा लगता है कि जब Erlang processes की संख्या किसी तय सीमा से ऊपर बढ़ती है, तो runtime को शायद कुछ अतिरिक्त memory space की ज़रूरत पड़ती है.
मेरा अनुमान है कि ऐसा supervision tree map या message queue capacity को पहले से allocate करके रखने की वजह से हो सकता है।
मुझे लगता है कि Rust paradigm से लेकर performance तक वाकई एक शानदार भाषा है।
async और thread तरीके की तुलना, और उसके साथ language runtimes को मिलाकर किए गए benchmarks नज़रिए के हिसाब से अलग लग सकते हैं, इसलिए इसे संदर्भ के तौर पर देखें।
HN की टिप्पणियाँ भी साथ में देखें। https://news.ycombinator.com/item?id=36024209