Kotlin में इनलाइन-फ़ंक्शन: ऑप्टिमाइज़ेशन का जादू या शुरुआती के लिए जाल?
\n\nयदि आपने कभी Kotlin में लिखा है और मानक लाइब्रेरी में देखा है, तो आपने निश्चित रूप से फ़ंक्शन घोषणाओं से पहले रहस्यमय शब्द inline देखा होगा। हो सकता है कि आपने इसे अपने कोड में भी जोड़ा हो, "अनुभवी" सहकर्मियों के उदाहरण का अनुसरण करते हुए। लेकिन क्या आप वास्तव में समझते हैं कि इनलाइन-फ़ंक्शन कब लाभ पहुँचाता है और कब यह केवल बाइटकोड को फुलाता है और संकलन को धीमा करता है?
यह प्रश्न Stack Overflow पर सबसे लोकप्रिय में से एक है (हज़ारों बार देखा गया!), और यह व्यर्थ नहीं है। हम शुष्क मैनुअल की नकल नहीं करेंगे। इसके बजाय, हम तीन वास्तविक परिदृश्यों का विश्लेषण करेंगे जहाँ इनलाइन-फ़ंक्शन आपके सबसे अच्छे दोस्त बन जाते हैं, और एक जहाँ उन्हें छोड़ देना बेहतर है। चलिए शुरू करते हैं!
\n\n30 सेकंड में इनलाइन-फ़ंक्शन क्या है (याद ताज़ा करें)
\n\nजब आप किसी फ़ंक्शन को कीवर्ड inline से चिह्नित करते हैं, तो कंपाइलर उसके लिए मेमोरी में एक अलग ऑब्जेक्ट नहीं बनाता है। इसके बजाय, यह सचमुच फ़ंक्शन के बॉडी को हर उस स्थान पर "चिपका" देता है जहाँ इसे कॉल किया जाता है। कल्पना करें कि आप कोड के एक टुकड़े को कॉपी कर रहे हैं और इसे सीधे कॉल पॉइंट पर पेस्ट कर रहे हैं - हुड के नीचे यही होता है।
बिना inline के:
\nfun measureTime(action: () -> Unit) {\n val start = System.currentTimeMillis()\n action()\n println("Time: ${System.currentTimeMillis() - start}ms")\n}\n\nfun main() {\n measureTime { /* आपका कोड */ }\n}\n\ninline के साथ:
\ninline fun measureTime(action: () -> Unit) {\n val start = System.currentTimeMillis()\n action()\n println("Time: ${System.currentTimeMillis() - start}ms")\n}\n\nfun main() {\n measureTime { /* आपका कोड */ }\n}\n\nअंतर आँखों से दिखाई नहीं देता, लेकिन कंपाइलर पूरी तरह से अलग बाइटकोड बनाएगा। पहले मामले में, एक अनाम लैम्ब्डा ऑब्जेक्ट बनाया जाएगा, दूसरे में - कोड बस कॉपी हो जाएगा। अब समझते हैं कि यह वास्तव में कब महत्वपूर्ण है।
\n\nपरिदृश्य #1: लैम्ब्डा के साथ Higher-order फ़ंक्शन (सबसे सामान्य मामला)
\n\nयदि आपका फ़ंक्शन किसी अन्य फ़ंक्शन को पैरामीटर के रूप में लेता है (तथाकथित higher-order function), तो लैम्ब्डा का प्रत्येक कॉल एक अनाम वर्ग बनाता है। कोड के हॉट सेक्शन पर, जो प्रति सेकंड हज़ारों बार कॉल किया जाता है, यह निम्नलिखित का कारण बन सकता है:
\n- \n
- हीप में ऑब्जेक्ट्स का अनावश्यक आवंटन \n
- गार्बेज कलेक्टर पर भार \n
- 10-20% तक प्रदर्शन हानि \n
inline कब बचाता है:
\n- \n
- आप एक ही फ़ंक्शन को लैम्ब्डा के साथ लूप में बार-बार कॉल करते हैं \n
- आपका फ़ंक्शन किसी लाइब्रेरी का हिस्सा है, और आप नहीं जानते कि इसका उपयोग कैसे किया जाएगा \n
- लैम्ब्डा कई वेरिएबल्स को कैप्चर करता है (inline ओवरहेड को कम करता है) \n
// बिना inline के खराब (यदि बार-बार कॉल किया जाता है)\nfun repeatAction(times: Int, action: () -> Unit) {\n for (i in 0 until times) {\n action()\n }\n}\n\n// inline के साथ अच्छा\ninline fun repeatAction(times: Int, action: () -> Unit) {\n for (i in 0 until times) {\n action()\n }\n}\n\nलेकिन एक बारीक बात है: यदि आपका फ़ंक्शन शायद ही कभी कॉल किया जाता है (मिनट में एक बार) या लैम्ब्डा बड़ा है, तो inline का लाभ लगभग शून्य हो जाता है। "बस मामले में" inline न बनाएं।
\n\nपरिदृश्य #2: Reified generics - रनटाइम पर प्रकारों के साथ काम करने का एकमात्र तरीका
\n\nJava और Kotlin में, जेनेरिक्स संकलन के दौरान मिटा दिए जाते हैं। आप सामान्य फ़ंक्शन के अंदर if (value is T) या T::class.java नहीं लिख सकते। लेकिन inline और reified के साथ, यह संभव हो जाता है!
वास्तविक जीवन का उदाहरण - प्रकार के अनुसार इंस्टेंस बनाना:
\n\n// यह संकलित नहीं होगा\nfun <T> createInstance(): T {\n return T::class.java.newInstance() // त्रुटि!\n}\n\n// और यह - काम करता है!\ninline fun <reified T> createInstance(): T {\n return T::class.java.newInstance()\n}\n\ninline के बिना, आप reified का उपयोग नहीं कर सकते। यह एकमात्र मामला है जहाँ inline अनिवार्य है, न कि केवल