कोटलिन में इनलाइन फ़ंक्शन का उपयोग कब करें?


105

मुझे पता है कि इनलाइन फ़ंक्शन शायद प्रदर्शन में सुधार करेगा और उत्पन्न कोड को बढ़ने का कारण होगा, लेकिन मुझे यकीन नहीं है कि जब यह एक का उपयोग करने के लिए सही है।

lock(l) { foo() }

पैरामीटर के लिए एक फ़ंक्शन ऑब्जेक्ट बनाने और कॉल करने के बजाय, कंपाइलर निम्नलिखित कोड का उत्सर्जन कर सकता है। ( स्रोत )

l.lock()
try {
  foo()
}
finally {
  l.unlock()
}

लेकिन मैंने पाया कि कोलाइन के द्वारा बिना इनलाइन फ़ंक्शन के कोई फंक्शन ऑब्जेक्ट नहीं बनाया गया है। क्यों?

/**non-inline function**/
fun lock(lock: Lock, block: () -> Unit) {
    lock.lock();
    try {
        block();
    } finally {
        lock.unlock();
    }
}

7
इसके लिए दो मुख्य उपयोग के मामले हैं, एक निश्चित प्रकार के उच्चतर कार्य के साथ है, और दूसरा अन्य प्रकार के मापदंडों का पालन करने वाला है। इनलाइन फ़ंक्शन के प्रलेखन में ये शामिल हैं: kotlinlang.org/docs/reference/inline-functions.html
zsmb13

2
@ zsmb13 धन्यवाद, सर। लेकिन मैं यह नहीं समझता कि: "पैरामीटर के लिए एक फंक्शन ऑब्जेक्ट बनाने और कॉल करने के बजाय, कंपाइलर निम्नलिखित कोड का उत्सर्जन कर सकता है"
होली-जावा

2
मुझे वह उदाहरण या तो tbh नहीं मिला।
गंदी_विलास

जवाबों:


280

मान लें कि आप एक उच्च क्रम फ़ंक्शन बनाते हैं जो प्रकार का लंबर लेता है () -> Unit(कोई पैरामीटर, कोई वापसी मान नहीं), और इसे इस तरह निष्पादित करता है:

fun nonInlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

जावा पार्लेंस में, यह कुछ इस तरह से अनुवाद करेगा (सरलीकृत!):

public void nonInlined(Function block) {
    System.out.println("before");
    block.invoke();
    System.out.println("after");
}

और जब आप इसे कोटलिन से ...

nonInlined {
    println("do something here")
}

हुड के तहत, यहां एक उदाहरण Functionबनाया जाएगा, जो लैम्बडा के अंदर कोड को लपेटता है (फिर से, यह सरलीकृत किया गया है):

nonInlined(new Function() {
    @Override
    public void invoke() {
        System.out.println("do something here");
    }
});

इसलिए मूल रूप से, इस फ़ंक्शन को कॉल करना और एक लैम्बडा को पास करना हमेशा एक Functionवस्तु का एक उदाहरण बना देगा ।


दूसरी ओर, यदि आप inlineकीवर्ड का उपयोग करते हैं :

inline fun inlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

जब आप इसे इस तरह कहते हैं:

inlined {
    println("do something here")
}

कोई Functionउदाहरण नहीं बनाया जाएगा, इसके बजाय, blockइनलाइन फ़ंक्शन के अंदर के कोड को कॉल साइट पर कॉपी किया जाएगा, इसलिए आपको बाईटकोड में कुछ इस तरह मिलेगा:

System.out.println("before");
System.out.println("do something here");
System.out.println("after");

इस मामले में, कोई नया उदाहरण नहीं बनाया गया है।


19
पहली जगह में फंक्शन ऑब्जेक्ट रैपर होने का क्या फायदा है? अर्थात - सब कुछ इनबिल्ट क्यों नहीं है?
आर्टर्स वैंकेन्स

14
इस तरह से आप मनमाने ढंग से कार्यों को मापदंडों के रूप में पास कर सकते हैं, उन्हें चर में स्टोर कर सकते हैं, आदि
zsmb13

6
@ Zsmb13
Yajairo87

2
तुम्हें पता है, और यदि आप उन लोगों के साथ बातें जटिल है, आप अंततः के बारे में जानना चाहता हूँ कर सकते हैं noinlineऔर crossinlineकीवर्ड - देख डॉक्स
zsmb13

2
डॉक्स एक कारण देता है जिसे आप डिफ़ॉल्ट रूप से इनलाइन नहीं करना चाहेंगे: इनलाइनिंग उत्पन्न कोड को बढ़ने का कारण हो सकता है; हालाँकि, अगर हम इसे एक उचित तरीके से करते हैं (यानी बड़े कार्यों को टालने से), तो यह प्रदर्शन में भुगतान करेगा, विशेष रूप से छोरों के अंदर "मेगामॉर्फिक" कॉल-साइट्स पर।
कोरेथन

43

मुझे जोड़ने दें: "जब उपयोग न करें inline" :

1) यदि आपके पास एक सरल कार्य है जो अन्य कार्यों को एक तर्क के रूप में स्वीकार नहीं करता है, तो इससे उन्हें इनलाइन करने का कोई मतलब नहीं है। IntelliJ आपको चेतावनी देगा:

इनलाइनिंग का अपेक्षित प्रदर्शन प्रभाव ... '' महत्वहीन है। कार्यात्मक प्रकार के मापदंडों के साथ कार्यों के लिए इनलाइनिंग सबसे अच्छा काम करता है

2) यहां तक ​​कि अगर आपके पास "कार्यात्मक प्रकार के मापदंडों के साथ" एक फ़ंक्शन है, तो आप कंपाइलर को बता सकते हैं कि इनलाइनिंग काम नहीं करती है। इस उदाहरण पर विचार करें:

inline fun calculateNoInline(param: Int, operation: IntMapper): Int {
    val o = operation //compiler does not like this
    return o(param)
}

यह कोड त्रुटि के साथ संकलित नहीं होगा:

इनलाइन-पैरामीटर 'ऑपरेशन' का अवैध उपयोग '...' में। पैरामीटर घोषणा में 'noinline' संशोधक जोड़ें।

कारण यह है कि संकलक इस कोड को इनलाइन करने में असमर्थ है। यदि operationकिसी वस्तु में लपेटा नहीं गया है (जो inlineआपको इससे बचना चाहता है), तो इसे एक चर में कैसे सौंपा जा सकता है? इस मामले में, कंपाइलर तर्क बनाने का सुझाव देता है noinline। एक inlineसमारोह के साथ एक noinlineसमारोह होने का कोई मतलब नहीं है, ऐसा मत करो। हालांकि, यदि कार्यात्मक प्रकार के कई पैरामीटर हैं, तो उनमें से कुछ को आवश्यक मानने पर विचार करें।

तो यहाँ कुछ सुझाव दिए गए नियम हैं:

  • आप इनलाइन कर सकते हैं जब सभी कार्यात्मक प्रकार के मापदंडों को सीधे बुलाया जाता है या अन्य इनलाइन फ़ंक्शन को पास किया जाता है
  • जब मामला हो तो आपको इनलाइन करना चाहिए
  • जब फ़ंक्शन पैरामीटर को फ़ंक्शन के अंदर एक चर में सौंपा जा रहा है तो आप इनलाइन नहीं कर सकते
  • आपको चाहिए इनलाइनिंग पर विचार यदि आपके कम से कम एक कार्यात्मक प्रकार के मापदंडों को इनलाइन किया जा सकता है, noinlineतो दूसरों के लिए उपयोग करें ।
  • आप विशाल कार्यों को इनलाइन नहीं करना चाहिए , उत्पन्न बाइट कोड के बारे में सोचना चाहिए । इसे उन सभी स्थानों पर कॉपी किया जाएगा जहां से फ़ंक्शन को कॉल किया जाता है।
  • एक अन्य उपयोग का मामला reifiedटाइप पैरामीटर है, जिसे आपको उपयोग करने की आवश्यकता है inline। पढ़ें यहाँ

4
तकनीकी रूप से आप अभी भी इनलाइन फ़ंक्शंस कर सकते हैं जो लैम्ब्डा एक्सप्रेशन को सही नहीं लेते हैं? .. यहाँ फायदा यह है कि फंक्शन कॉल ओवरहेड को उस स्थिति में टाला जाता है .. स्कैला जैसी भाषा इस बात की अनुमति देती है .. निश्चित नहीं है कि कोटलिन उस तरह के इनलाइन को मना क्यों करता है- इंग
रग-वन

3
@ दुष्ट-एक कोटलिन इनलाइनिंग के इस समय की मनाही नहीं करता है। भाषा के लेखक केवल यह दावा कर रहे हैं कि प्रदर्शन लाभ महत्वहीन होने की संभावना है। जेआईटी अनुकूलन के दौरान जेवीएम द्वारा छोटे तरीकों को पहले से ही इनवेट किया जा सकता है, खासकर यदि उन्हें अक्सर निष्पादित किया जाता है। एक अन्य मामला जहां inlineहानिकारक हो सकता है, जब कार्यात्मक पैरामीटर को इनलाइन फ़ंक्शन में कई बार कहा जाता है, जैसे कि विभिन्न सशर्त शाखाओं में। मैं सिर्फ एक मामले में भाग गया, जहां कार्यात्मक तर्कों के लिए सभी बायोटेक को इस वजह से दोहराया जा रहा था।
माइक हिल

5

सबसे महत्वपूर्ण मामला जब हम इनलाइन संशोधक का उपयोग करते हैं, जब हम पैरामीटर कार्यों के साथ उपयोग जैसे कार्यों को परिभाषित करते हैं। संग्रह या स्ट्रिंग प्रसंस्करण (जैसे filter, mapया joinToString) या केवल स्टैंडअलोन फ़ंक्शन एक आदर्श उदाहरण हैं।

यही कारण है कि इनलाइन संशोधक ज्यादातर पुस्तकालय डेवलपर्स के लिए एक महत्वपूर्ण अनुकूलन है। उन्हें पता होना चाहिए कि यह कैसे काम करता है और इसके सुधार और लागत क्या हैं। जब हम फ़ंक्शन प्रकार पैरामीटर के साथ अपने स्वयं के उपयोग कार्यों को परिभाषित करते हैं, तो हमें अपनी परियोजनाओं में इनलाइन संशोधक का उपयोग करना चाहिए।

यदि हमारे पास फ़ंक्शन प्रकार पैरामीटर, पुनरीक्षित प्रकार पैरामीटर नहीं है, और हमें गैर-स्थानीय रिटर्न की आवश्यकता नहीं है, तो हमें सबसे अधिक संभावना इनलाइन संशोधक का उपयोग नहीं करना चाहिए। यही कारण है कि हमारे पास एंड्रॉइड स्टूडियो या आईडिया इंटेलीज पर चेतावनी होगी।

इसके अलावा, एक कोड आकार की समस्या है। एक बड़े फ़ंक्शन को सम्मिलित करने से नाटकीय रूप से बाइटेकोड का आकार बढ़ सकता है क्योंकि यह हर कॉल साइट पर कॉपी किया जाता है। ऐसे मामलों में, आप फ़ंक्शन को रिफैक्ट कर सकते हैं और नियमित कार्यों के लिए कोड निकाल सकते हैं।


4

उच्च-क्रम के कार्य बहुत सहायक होते हैं और वे वास्तव reusabilityमें कोड में सुधार कर सकते हैं । हालांकि, उनका उपयोग करने के बारे में सबसे बड़ी चिंताओं में से एक दक्षता है। लैम्ब्डा अभिव्यक्तियों को कक्षाओं (अक्सर अनाम कक्षाओं) में संकलित किया जाता है, और जावा में ऑब्जेक्ट निर्माण एक भारी ऑपरेशन है। हम अभी भी उच्च-क्रम के कार्यों को प्रभावी तरीके से उपयोग कर सकते हैं, जबकि सभी लाभों को रखते हुए, फ़ंक्शन को इनलाइन बनाकर।

यहाँ तस्वीर में इनलाइन फ़ंक्शन आता है

जब एक फ़ंक्शन के रूप में चिह्नित किया जाता है inline, तो कोड संकलन के दौरान कंपाइलर सभी फ़ंक्शन कॉल को फ़ंक्शन के वास्तविक निकाय से बदल देगा। साथ ही, तर्क के रूप में दिए गए लंबोदर भावों को उनके वास्तविक शरीर के साथ बदल दिया जाता है। उन्हें कार्यों के रूप में नहीं, बल्कि वास्तविक कोड के रूप में माना जाएगा।

संक्षेप में: - इनलाइन -> बुलाए जाने के बजाय, संकलन के समय उन्हें फ़ंक्शन के बॉडी कोड द्वारा बदल दिया जाता है ...

कोटलिन में, एक फ़ंक्शन को दूसरे फ़ंक्शन के पैरामीटर के रूप में उपयोग करना (जिसे उच्च-क्रम फ़ंक्शन कहा जाता है) जावा की तुलना में अधिक प्राकृतिक लगता है।

लैम्ब्डा का उपयोग करने के कुछ नुकसान हैं, हालांकि। चूंकि वे अनाम कक्षाएं हैं (और इसलिए, ऑब्जेक्ट), उन्हें मेमोरी की आवश्यकता होती है (और यहां तक ​​कि आपके ऐप की समग्र विधि गणना में भी जोड़ सकते हैं)। इससे बचने के लिए, हम अपने तरीकों को इनलाइन कर सकते हैं।

fun notInlined(getString: () -> String?) = println(getString())

inline fun inlined(getString: () -> String?) = println(getString())

उपरोक्त उदाहरण से : - ये दोनों कार्य ठीक एक ही कार्य करते हैं - गेटस्ट्रिंग फ़ंक्शन के परिणाम को प्रिंट करना। एक अंतर्भूत है और एक नहीं है।

यदि आप विघटित जावा कोड की जाँच करते हैं, तो आप देखेंगे कि विधियाँ पूरी तरह से समान हैं। ऐसा इसलिए है क्योंकि इनलाइन कीवर्ड कॉल-साइट में कोड को कॉपी करने के लिए कंपाइलर को एक निर्देश है।

हालाँकि, अगर हम किसी फ़ंक्शन को नीचे दिए गए किसी अन्य फ़ंक्शन में पास कर रहे हैं:

//Compile time error… Illegal usage of inline function type ftOne...
 inline fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/
 }

इसे हल करने के लिए, हम अपने फ़ंक्शन को नीचे के रूप में फिर से लिख सकते हैं:

inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/}

मान लें कि हमारे पास नीचे की तरह एक उच्च क्रम फ़ंक्शन है:

inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/}

यहाँ, संकलक हमें इनलाइन कीवर्ड का उपयोग नहीं करने के लिए कहेगा जब केवल एक लैम्ब्डा पैरामीटर है और हम इसे किसी अन्य फ़ंक्शन में भेज रहे हैं। इसलिए, हम नीचे दिए गए फ़ंक्शन को फिर से लिख सकते हैं:

fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int) {
    //passing a function type to another function
    val funOne = someFunction(ftOne)
    /*...*/
}

नोट : -हम को कीवर्ड नॉयलाइन को भी हटाना पड़ा क्योंकि इसका उपयोग केवल इनलाइन फ़ंक्शन के लिए किया जा सकता है!

मान लीजिए हमारे पास इस तरह का कार्य है ->

fun intercept() {
    // ...
    val start = SystemClock.elapsedRealtime()
    val result = doSomethingWeWantToMeasure()
    val duration = SystemClock.elapsedRealtime() - start
    log(duration)
    // ...}

यह ठीक काम करता है, लेकिन फ़ंक्शन के तर्क का मांस माप कोड से प्रदूषित हो जाता है जिससे आपके सहयोगियों के लिए यह कठिन हो जाता है कि वे क्या काम कर रहे हैं। :)

यहां बताया गया है कि इनलाइन फ़ंक्शन इस कोड की सहायता कैसे कर सकता है:

      fun intercept() {
    // ...
    val result = measure { doSomethingWeWantToMeasure() }
    // ...
    }

     inline fun <T> measure(action: () -> T) {
    val start = SystemClock.elapsedRealtime()
    val result = action()
    val duration = SystemClock.elapsedRealtime() - start
    log(duration)
    return result
    }

अब मैं माप कोड की रेखाओं पर लंघन के बिना अवरोधन () फ़ंक्शन का मुख्य उद्देश्य क्या है, इसे पढ़ने पर ध्यान केंद्रित कर सकता हूं। हम उस कोड को अन्य स्थानों पर पुन: उपयोग करने के विकल्प से भी लाभान्वित होते हैं जहाँ हम चाहते हैं

इनलाइन आपको माप (लामाडा) जैसे लंबोदर को पारित करने के बजाय एक बंद ({...}) के भीतर एक लंबो तर्क के साथ एक फ़ंक्शन को कॉल करने की अनुमति देता है


2

एक साधारण मामला जहां आप एक चाहते हो सकता है जब आप एक उपयोग फ़ंक्शन बनाते हैं जो एक निलंबित ब्लॉक में होता है। इस पर विचार करो।

fun timer(block: () -> Unit) {
    // stuff
    block()
    //stuff
}

fun logic() { }

suspend fun asyncLogic() { }

fun main() {
    timer { logic() }

    // This is an error
    timer { asyncLogic() }
}

इस स्थिति में, हमारा टाइमर निलंबित कार्यों को स्वीकार नहीं करेगा। इसे हल करने के लिए, आपको इसे निलंबित करने के लिए भी लुभाया जा सकता है

suspend fun timer(block: suspend () -> Unit) {
    // stuff
    block()
    // stuff
}

लेकिन तब इसे केवल कोरटाइन / सस्पेंड फ़ंक्शंस से ही इस्तेमाल किया जा सकता है। फिर आप इन यूटिलियों का एक एसिंक्श वर्जन और एक नॉन-एसिंक्स वर्जन तैयार करेंगे। यदि आप इसे इनलाइन बनाते हैं तो समस्या दूर हो जाती है।

inline fun timer(block: () -> Unit) {
    // stuff
    block()
    // stuff
}

fun main() {
    // timer can be used from anywhere now
    timer { logic() }

    launch {
        timer { asyncLogic() }
    }
}

यहां त्रुटि राज्य के साथ एक कोटलिन खेल का मैदान है । इसे हल करने के लिए टाइमर इनलाइन बनाएं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.