RAII, Rust/Linux का भ्रम
(kristoff.it)सारांश
यह लेख Rust डेवलपर्स और मौजूदा Linux डेवलपर्स के बीच के विवाद को देखकर लिखा गया है। अलग-अलग डेवलपर्स की अपनी-अपनी coding style हो सकती है, लेकिन Linux प्रोजेक्ट पहले ही C++ को बाहर कर चुका है, ताकि उसकी code style और structure (RAII) से बचा जा सके.
Asahi Lina ने जिस तरह कोड के काम करने का ज़िक्र किया है, वह उस प्रोग्राम को बंद करते समय बहुत धीमा है, और performance-oriented software बनाने के सबसे बुनियादी तरीकों में से एक, यानी batch processing, के खिलाफ जाता है। उदाहरण के लिए, memory arena का उपयोग करके batch processing करने से कई lifetimes को एक साथ नियंत्रित किया जा सकता है, इसलिए RAII की ज़रूरत नहीं होती।
यहाँ मैं अपनी दलील का समर्थन करने वाली सामग्री दे रहा हूँ। ये सभी सामग्री बताती हैं कि batch processing क्यों अच्छी है:
- Casey Muratori | Smart-Pointers, RAII, ZII? Becoming an N+2 programmer
- CppCon 2014: Mike Acton "Data-Oriented Design and C++"
- Modern Systems Programming: Rust and Zig - Aleksey Kladov
इसलिए मेरा मानना है कि Linux को जीवन भर RAII को स्वीकार नहीं करना चाहिए।
मैं यह लेख यहाँ इसलिए लाया हूँ क्योंकि मैंने देखा कि कोरिया के Rust डेवलपर्स इस लेख को देखकर कई बार बहुत नाराज़ हुए। इसलिए मैं जानना चाहता था कि यहाँ के लोग इसके बारे में क्या सोचते हैं। आप क्या सोचते हैं?
11 टिप्पणियां
मेरी राय है, लेकिन कुछ डेवलपर्स का elitism कुछ हद तक समझ में आता है। software "engineering" के नज़रिए से, खासकर Linux जैसी चीज़ के मामले में, आज के open source जगत में ऐसा "software" ढूंढना मुश्किल है जिसने closed दुनिया के साथ भी व्यापक रूप से हाथ मिलाते हुए open source दर्शन की प्रगति में मदद की हो। शायद इसी वजह से वे एक और भी अधिक बहिष्कारी, यहाँ तक कि Luddite जैसी दिखने वाली रूढ़िवादी भूमिका बनाए रखते हैं—इस डर से कि अप्रमाणित प्रोग्रामर Rust को आगे रखकर बाढ़ की तरह अंदर आ जाएँगे, मौजूदा project maintenance के core लोगों के नियंत्रण से बाहर का code जगह-जगह जोड़ते जाएँगे, technical debt को बेतहाशा बढ़ाएँगे, और Linux के lifecycle को छोटा कर देंगे।
यह दिलचस्प है कि open source लंबे समय तक open source बना रहे, इसके लिए एक "open" न होने वाला रवैया अपनाया जाता है।
मैं भी अक्सर RAII या इसी तरह के resource management का इस्तेमाल करता हूँ और इसकी सिफारिश भी करता हूँ। क्योंकि RAII क्या है, यह ठीक से जाने बिना भी अगर कोई इसे बिना ज़्यादा सोचे इस्तेमाल करे, तब भी कम-से-कम “फ़िलहाल के लिए सुरक्षित” कोड निकल आता है।
लेकिन अगर इसे ठीक से समझकर न इस्तेमाल किया जाए, तो ऐसी अक्षम कोड की भरमार होना आसान है जिसमें किसी फ़ाइल को सिर्फ़ एक बार खोलना काफ़ी होता, फिर भी उसे दर्जनों बार खोलना-बंद करना पड़ता है। मेरा मानना है कि अगर डेवलपर लगातार performance पर ध्यान दे रहा हो और ऐसी संस्कृति dev team में पहले से मौजूद हो, तो RAII के साथ भी काफ़ी अच्छा performance हासिल किया जा सकता है।
freeचलानाfreeकरना है उन्हें इकट्ठा करके bulk में चलाना?Linux में क्या ऐसा कोई फीचर या API है जो 2 को 1 से ज़्यादा तेज़ चलने दे?
मैं तो स्वाभाविक रूप से हमेशा 1 के साथ ही रहा हूँ, इसलिए ठीक से समझ नहीं पा रहा हूँ।
डेवलपमेंट पूरा करने के बाद memory leak ढूंढने के लिए valgrind चलाने वाले डेवलपर अनुभव में मैं फिर से वापस नहीं जाना चाहता।
मुझे ठीक-ठीक पता नहीं, लेकिन अगर RAII का उपयोग नहीं करने की बात है, तो ऐसा लगता है कि जानबूझकर memory leak का इस्तेमाल करके (close) performance बढ़ाने की कोशिश की जा रही है; लेकिन यही सही दिशा है या नहीं, इसे लेकर मुझे यक़ीन नहीं है.
वैसे भी, जो डेवलपर manual memory management अच्छी तरह कर सकता है, वह RAII भी अच्छी तरह इस्तेमाल करेगा, और जो डेवलपर RAII के बिना development नहीं कर सकता, वह memory को manually manage भी नहीं कर पाएगा, इसलिए मुझे नहीं लगता कि RAII का इस्तेमाल न करने की कोई वजह है.
मैं यह जानना चाहता था कि
freeकितना समय खाता है, इसलिए असली workload से काफ़ी अलग होने पर भी मैंने एक साधारण कोड लिखकर टेस्ट किया। (Rust release build में,std::alloc::allocऔरstd::fs::Fileका इस्तेमाल किया।)विभिन्न आकार की memory की 10,000,000 allocations, कुल लगभग 2.5GB, पहले से करके रखीं और सिर्फ़ उन्हें मुक्त करने में लगने वाला समय मापा, तो 1.87 सेकंड लगे। यानी प्रति allocation 187ns।
इसके विपरीत, फ़ाइल के मामले में सिर्फ़ लगभग 10,000 handles खोलकर रखे और उन्हें बंद करने में लगने वाला समय मापा, तो लगभग 9 सेकंड लगे। यानी एक फ़ाइल पर 900us लगे।
(शायद इस Windows PC में antivirus की वजह से file kaam असामान्य रूप से धीमे हैं। दूसरे Windows laptop पर क्रमशः 400ns/200us, और दूसरे Linux PC पर 50ns/600ns लगे।)
RAII के विकल्प के रूप में bulk processing या process समाप्ति पर OS पर भरोसा करके resources को leak कर देने की बात अक्सर की जाती है। memory के लिए यह आसानी से संभव लगता है।
लेकिन file या socket जैसे resources के लिए मैंने bulk reclamation API कभी नहीं देखी, और अगर resources को leak किया जाए, तो user code का समय भले कम हो जाए, जितना समय बचता है उतना ही लगभग kernel को process बंद करने में ज़्यादा लग जाता है, इसलिए performance का फ़ायदा ज़्यादा नहीं होता।
memory RAII अपेक्षाकृत उतना धीमा भी नहीं है, न ही यह arena के इस्तेमाल को असंभव बनाता है, और ज़रूरत हो तो intentional leak करने से भी नहीं रोकता, इसलिए RAII से बचने का यह कोई मज़बूत कारण नहीं लगता।
और जो file RAII इससे भी धीमा है, उसमें न तो bulk में निपटाने का तरीका है और न ही इसकी लागत से बचने का कोई उपाय, तो ऐसी स्थिति में RAII के विकल्प वास्तव में कितने बेहतर हैं, यह जानने की जिज्ञासा है।
थोड़ा विषयांतर है, लेकिन मुझे ऐसा लगता है कि RAII और lifetime पर जो आपत्तियाँ आती हैं, वे अक्सर
malloc/freeसे प्रतिनिधित्व होने वाले memory resources तक ही सीमित रह जाती हैं।RAII और lifetime सिर्फ़ memory allocation के लिए ही नहीं, बल्कि file, socket, lock जैसे OS resources, साथ ही object pool, connection pool आदि जैसे
उन अधिकांश resource models के लिए भी व्यापक रूप से उपयोगी हैं, जिनमें acquire और return होता है, और acquire के दौरान exclusive access control की आवश्यकता होती है।
ये resources भी
malloc/freeजैसी ही संरचना साझा करते हैं, इसलिए इनमें leak, use after free, double free जैसी समस्याओं की संरचनात्मक समानता भी होती है,और इसी साझा संरचना की वजह से RAII और lifetime सिर्फ़ memory ही नहीं बल्कि ऐसे resources की समस्याओं को भी एक साथ हल कर सकते हैं—मुझे लगता है इस पहलू पर और अधिक ध्यान दिया जाना चाहिए।
उदाहरण के लिए Rust में file handle के लिए भी use after close और double close को compile time पर रोका जाता है:
https://play.rust-lang.org/?version=stable&mode=debug&edition=…
मुख्य GC भाषाएँ memory को GC से manage करती हैं, लेकिन आख़िरकार file handle, socket जैसे ऐसे resources के लिए, जिनका management deterministic होना चाहिए,
RAII जैसी संरचनाएँ (Java का try-with-resources statement, C# का using statement, Python का with statement आदि) या मिलती-जुलती संरचनाएँ (Go का
deferआदि)अलग से जोड़ती हैं, और अंत में एक ही भाषा में resource management के कई modes हो जाते हैं—मुझे लगता है, उससे यह तरीका शायद बेहतर है।
Asahi Lina가...से शुरू होने वाले पैराग्राफ से आगे दिए गए सभी लिंक कई-दस मिनट लंबे YouTube वीडियो हैं, इसलिए उन्हें देखना मुश्किल है। अगर आप ऐसे तकनीकी उदाहरण दे सकें जो केस-आधारित हों और दिखाएँ कि किन स्थितियों में RAII की जरूरत नहीं पड़ती, तो चर्चा और सक्रिय हो सकती है.अगर आपका मतलब arena से है, तो Rust में भी arena स्वाभाविक रूप से मौजूद है, और lifetime के जरिए arena को समाप्त करके "batch free" के बाद arena के elements तक पहुँच को प्रतिबंधित करना भी संभव है। कृपया https://crates.io/keywords/arena देखें.
मुझे उम्मीद है कि zig या rust के बाद भी और बहुत-सी भाषाएँ आएँगी। लेकिन अभी तक मैंने rust जितनी उपयुक्त भाषा नहीं देखी है। बल्कि मुझे लगता है कि ऐसी भाषाओं के बीच होने वाली चर्चाओं से सामने आने वाला डेवलपर्स का ज्ञान ज़्यादा उपयोगी होता है। हाहा..
मैं भी अपने स्तर पर Rust को मुख्य भाषा के रूप में इस्तेमाल करने वाला डेवलपर हूँ, और मुझे गुस्सा तो नहीं आया, लेकिन ऐसा लगा कि कहीं यह कुछ ज़्यादा ही चरम उदाहरण ("वह प्रोग्राम बंद करते समय बहुत धीमा होता है" कहकर, और लिंक किए गए वीडियो में भी Rust प्रोजेक्ट के किसी सीधे जुड़े मामले के बजाय यह उदाहरण दिया गया है कि Visual Studio बंद करते समय हर अलग component का destructor कॉल होने की वजह से बहुत समय लगता है) लेकर तो नहीं आ रहे हैं।
परफ़ॉर्मेंस की दृष्टि से अगर कई components का clean-up एक साथ प्रोसेस होना ज़रूरी हो, तो हर component के लिए अलग से Drop इम्प्लीमेंट करने के बजाय, उन components की lifetime को होल्ड करने वाले type में Drop इम्प्लीमेंट करके clean-up को एक बार में कराया जा सकता है। अगर यह safety guard भी रखा जाए कि उन components को केवल उसी type के API के ज़रिए बनाया जा सके, तो और भी बेहतर होगा।
बेशक, ऊपर के लेख के लेखक की चिंता शायद यह है कि अगर RAII का उपयोग करने की प्रथा Linux codebase में आ गई, तो इतने विशाल codebase की जटिलता के बीच ऐसे code जमा होते जा सकते हैं जिनमें बहुत अप्रत्यक्ष performance concerns हों, और लंबे समय में Visual Studio जैसी स्थिति पैदा हो सकती है। यह निश्चित रूप से चिंता करने लायक बिंदु लगता है। लेकिन, जैसा कि दूसरे comment में कहा गया, RAII जो stability देता है वह भी है, इसलिए मुझे लगता है कि यह कुछ हद तक trade-off का मामला है।
दोनों पक्ष सही बात कर रहे हैं।
उदाहरण के तौर पर, online game LoL के character Azir को split push संचालन, team fight में zone control, और ultimate value के मामले में बेहद मजबूत high-tier champion माना जाता है, लेकिन यह बात सिर्फ़ बेहद प्रशिक्षित pro मैचों में लागू होती है; आम खिलाड़ी स्तर पर उसकी lane phase भी बहुत कमजोर होती है और उसका base power भी कम होता है, इसलिए वह बस एक lowest-tier champion बनकर रह जाता है।
Asahi Lina जैसे, programming और operating system की जानकारी रखने वाले top 10% लोगों के नज़रिए से RAII के अलावा दूसरे विकल्प स्वाभाविक रूप से बेहतर लग सकते हैं, लेकिन बाकी 90% जिन हिस्सों को संभालते हैं, वहाँ मुझे लगता है कि RAII या Rust से बेहतर कुछ नहीं है।
लेकिन memory stability/safety को सुनिश्चित करने की एक बड़ी वजह security issue भी है... इसलिए मुझे लगता है कि tradeoff अपरिहार्य है।
ऐसा लगता है कि
raiiके बिना अपेक्षाकृत कम अनुभव वाले डेवलपर बहुत सारे bugs पैदा करेंगेकम से कम OS नहीं, application level पर तो...