आधुनिक कंपाइलरों में फ़ंक्शन कॉल की लागत अभी भी क्या मायने रखती है?


95

मैं एक धार्मिक व्यक्ति हूं और पाप नहीं करने के लिए प्रयास करता हूं। यही कारण है कि मैं रॉबर्ट सी। मार्टिन को फिर से लिखने के लिए छोटे ( उससे छोटे) लिखता हूं , जो क्लीन कोड बाइबिल द्वारा आदेशित कई आदेशों का पालन करता है । लेकिन कुछ सामान की जाँच करते समय, मैं इस पोस्ट पर उतरा , जिसके नीचे मैंने यह टिप्पणी पढ़ी:

याद रखें कि भाषा के आधार पर एक विधि कॉल की लागत महत्वपूर्ण हो सकती है। पढ़ने योग्य कोड लिखने और प्रदर्शन करने वाले कोड लिखने के बीच लगभग हमेशा एक व्यापार होता है।

किन परिस्थितियों में यह कथन अभी भी मान्य है कि आजकल के प्रदर्शनकारी आधुनिक संकलकों का समृद्ध उद्योग है?

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


11
पठनीय और रख-रखाव कोड लिखें। केवल जब आप स्टैक ओवरफ्लो के साथ एक समस्या का सामना करते हैं तो आप अपने नीच विचार कर सकते हैं
Fabio

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

6
व्यक्तिगत अनुभव से बोलते हुए, मैं एक मालिकाना भाषा में कोड लिखता हूं जो क्षमता के मामले में काफी आधुनिक है, लेकिन फ़ंक्शन कॉल हास्यास्पद रूप से महंगे हैं, इस बिंदु पर जहां छोरों के लिए भी विशिष्ट गति के लिए अनुकूलित किया जाना है: for(Integer index = 0, size = someList.size(); index < size; index++)बस के बजाय for(Integer index = 0; index < someList.size(); index++)। सिर्फ इसलिए कि आपका कंपाइलर पिछले कुछ सालों में बना था, इसका मतलब यह नहीं है कि आप प्रोफाइलिंग कर सकते हैं।
फ़िरफ़ॉक्स

5
@phyrfox जो समझ में आता है, लूप के माध्यम से हर बार कॉल करने के बजाय लूप के बाहर someList.size () का मान प्राप्त करना। यह विशेष रूप से सच है अगर एक सिंक्रनाइज़ेशन समस्या का कोई मौका है जहां पाठक और लेखक पुनरावृति के दौरान टकराव की कोशिश कर सकते हैं, तो उस स्थिति में आप पुनरावृत्ति के दौरान किसी भी परिवर्तन के खिलाफ सूची की रक्षा करना चाहते हैं।
क्रेग

8
बहुत दूर तक छोटे कार्य करने से सावधान रहें, यह कोड को उतनी ही कुशलता से लागू कर सकता है जितना कि एक अखंड मेगा-फंक्शन करता है। यदि आप मुझ पर विश्वास नहीं करते हैं, तो ioccc.org विजेताओं में से कुछ की जाँच करें : कुछ कोड सब कुछ एक में main(), दूसरों ने कुछ 50 छोटे कार्यों में विभाजित किया, और सभी पूरी तरह से अपठनीय हैं। चाल, हमेशा की तरह, एक अच्छा संतुलन बनाने के लिए है
13

जवाबों:


148

यह आपके डोमेन पर निर्भर करता है।

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

और संकलक का प्रश्न आपके लिए तरीकों को सम्मिलित करने का भी है। अधिकांश संकलक इनलाइन कार्यों के लिए पर्याप्त बुद्धिमान हैं जहां यह संभव है।

और अंतिम, प्रदर्शन का सुनहरा नियम है: हमेशा शख्सियत। मान्यताओं के आधार पर "अनुकूलित" कोड न लिखें। यदि आप असामान्य हैं, तो दोनों मामलों को लिखें और देखें कि कौन सा बेहतर है।


13
और उदाहरण के लिए हॉटस्पॉट कंपाइलर सट्टा इनलाइनिंग करता है, जो कि कुछ अर्थों में है, जबकि यह संभव नहीं है।
जॉर्ग डब्ल्यू मित्तग

49
वास्तव में, एक वेब एप्लिकेशन में, डीबी पहुंच और नेटवर्क ट्रैफिक के संबंध में शायद पूरा कोड नगण्य है ...
AnoE

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

2
@ मेहरदाद इस मामले में भी मुझे आश्चर्य होगा अगर कोड में अनुकूलन करने के लिए अधिक प्रासंगिक कुछ भी नहीं था। कोड को प्रोफाइल करते समय मैं चीजों को कॉल की तुलना में भारी देखता हूं, और यही वह जगह है जहां अनुकूलन के लिए प्रासंगिक है। कुछ देव एक या दो अडॉप्ट किए गए LOC के लिए पागल हो जाते हैं, लेकिन जब आप SW को प्रोफाइल करते हैं तो आपको पता चलता है कि यह डिज़ाइन इस से अधिक मायने रखता है, कम से कम कोड के सबसे बड़े हिस्से के लिए। जब आप अड़चन पाते हैं तो आप इसे अनुकूलित करने की कोशिश कर सकते हैं, और यह निम्न स्तर के मनमाने ढंग से अनुकूलन की तुलना में बहुत अधिक प्रभाव पड़ेगा जैसे कि कॉल ओवरहेड से बचने के लिए बड़े कार्य लिखना।
टिम

8
अच्छा उत्तर! आपका अंतिम बिंदु पहले होना चाहिए: हमेशा यह तय करने से पहले प्रोफ़ाइल करें कि कहां अनुकूलन करना है
सीजे डेनिस

56

फंक्शन कॉल ओवरहेड पूरी तरह से भाषा पर निर्भर करता है, और आप किस स्तर पर अनुकूलन कर रहे हैं।

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

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

लेकिन सॉफ्टवेयर का विशाल बहुमत इतना प्रदर्शन-महत्वपूर्ण नहीं है कि आप फ़ंक्शन कॉल को ओवरहेड नोटिस कर पाएंगे। किसी भी मामले में, साफ, सरल कोड लिखना बंद हो जाता है:

  • यदि आपका कोड प्रदर्शन-महत्वपूर्ण नहीं है, तो इससे रखरखाव आसान हो जाता है। प्रदर्शन-महत्वपूर्ण सॉफ़्टवेयर में भी, अधिकांश कोड "हॉट स्पॉट" नहीं होगा।

  • यदि आपका कोड प्रदर्शन-महत्वपूर्ण है, तो सरल कोड अनुकूलन के लिए कोड और स्पॉट के अवसरों को समझना आसान बनाता है। सबसे बड़ी जीत आमतौर पर माइक्रो-ऑप्टिमाइज़ेशन जैसे इनलाइनिंग फ़ंक्शन से नहीं होती है, लेकिन एल्गोरिथम सुधार से होती है। या अलग ढंग से प्रकाशित: एक ही काम तेजी से मत करो। कम करने का तरीका खोजें।

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


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

1
संज्ञानात्मक ओवरहेड के अलावा, डिबगर जानकारी में प्रतीकात्मक ओवरहेड है, और आमतौर पर अंतिम बायनेरिज़ में ओवरहेड अपरिहार्य है।
फ्रैंक हिलमैन

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

19

प्रदर्शन के लिए ट्यूनिंग कोड के बारे में लगभग सभी कहावतें आमदाल के कानून के विशेष मामले हैं । अमदहल के कानून का संक्षिप्त, विनोदी कथन है

यदि आपके प्रोग्राम का एक टुकड़ा रनटाइम का 5% लेता है, और आप उस टुकड़े को ऑप्टिमाइज़ करते हैं, तो यह अब शून्य प्रतिशत रनटाइम लेता है, पूरे के रूप में प्रोग्राम केवल 5% तेजी से होगा।

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

यही कारण है कि लोग आमतौर पर फ़ंक्शन कॉल की लागत के बारे में चिंता नहीं करने के लिए कहते हैं: कोई फर्क नहीं पड़ता कि वे कितने महंगे हैं, आम तौर पर कार्यक्रम एक पूरे के रूप में केवल कॉल ओवरहेड पर इसके रनटाइम का एक छोटा सा हिस्सा खर्च कर रहा है, इसलिए उन्हें गति देना बहुत मदद नहीं करता है ।

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

और, यदि आप विश्वास है कि एक कार्यक्रम के कारण है इसकी क्रम का एक बहुत खर्च सिर्फ समारोह में कॉल करने, तो आप के बारे में उन फ़ंक्शन कॉल में से कुछ अनावश्यक हैं कि क्या सोचना शुरू कर देना चाहिए। यह जानने के लिए कि आपको ऐसा कब करना चाहिए:

  • यदि किसी फ़ंक्शन का प्रति-आमंत्रण रनटाइम एक मिलीसेकंड से कम है, लेकिन उस फ़ंक्शन को सैकड़ों हजारों बार कहा जाता है, तो यह संभवतः इनलाइन होना चाहिए।

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

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


7
बस नेस्टेड छोरों में गहरे किए गए महंगे सामान से सावधान रहें। मैंने एक फ़ंक्शन को अनुकूलित किया है और कोड प्राप्त किया है जो तेजी से 10x चलता है। प्रोफाइलर द्वारा अपराधी को इंगित किए जाने के बाद वह था। (इसे ओवर और ओवर कहा जाता था, लूप्स में O (n ^ 3) से छोटे n O (n ^ 6) तक।)
लोरेन Pechtel

"दुर्भाग्य से, इसका एकमात्र इलाज कुछ परतों से छुटकारा पाना है, जो अक्सर बहुत कठिन होता है।" - यह आपकी भाषा संकलक और / या आभासी मशीन प्रौद्योगिकी पर बहुत निर्भर करता है। यदि आप संकलक इनलाइन को आसान बनाने के लिए कोड को संशोधित कर सकते हैं (उदाहरण के लिए finalवर्गों और विधियों का उपयोग करके जहां जावा में लागू होता है, या गैर virtual# तरीके C # या C ++ में) तो अप्रत्यक्ष संकलक / रनटाइम द्वारा समाप्त किया जा सकता है और आप बड़े पैमाने पर पुनर्गठन के बिना एक लाभ देखेंगे। जैसा कि @JorgWMittag ऊपर बताते हैं, JVM उन मामलों में भी इनलाइन कर सकता है, जहां यह साबित नहीं होता है कि ऑप्टिमाइज़ेशन है ...
Jules

... मान्य है, इसलिए यह अच्छी तरह से हो सकता है कि यह वैसे भी लेयरिंग के बावजूद आपके कोड में कर रहा है।
जूल्स

@ जूल्स हालांकि यह सच है कि जेआईटी संकलक सट्टा अनुकूलन कर सकते हैं, इसका मतलब यह नहीं है कि इस तरह के अनुकूलन समान रूप से लागू होते हैं । जावा के बारे में विशेष रूप से, मेरा अनुभव यह है कि डेवलपर संस्कृति परतों के शीर्ष पर परतदार परतों का पक्ष लेती है जो अत्यंत गहरी कॉल स्टैक की ओर ले जाती हैं। वास्तविक रूप से, यह कई जावा अनुप्रयोगों के सुस्त, फूला हुआ महसूस करने में योगदान देता है। इस तरह की अत्यधिक स्तरित वास्तुकला जेआईटी रनटाइम के खिलाफ काम करती है, भले ही यह परतें तकनीकी रूप से अक्षम हों। JIT एक जादुई गोली नहीं है जो स्वचालित रूप से संरचनात्मक समस्याओं को ठीक कर सकती है।
अमोन

@ लैमन "लसग्ना कोड" के साथ मेरा अनुभव बहुत बड़े सी ++ अनुप्रयोगों से आता है, जिसमें 1990 के दशक में बहुत सारे कोड डेटिंग होते हैं, जब गहरी नेस्टेड वस्तु पदानुक्रम और COM फैशन थे। सी ++ कंपाइलर इस तरह के कार्यक्रमों में अमूर्त दंड को कुचलने के लिए काफी वीर प्रयासों में जाते हैं, और फिर भी आप उन्हें अप्रत्यक्ष-शाखा पाइपलाइन स्टालों (और आई-कैश मिसेज पर एक और महत्वपूर्ण हिस्सा) पर दीवार-घड़ी रनटाइम का एक महत्वपूर्ण अंश खर्च करते देख सकते हैं। ।
zwol

17

मैं इस उद्धरण को चुनौती दूंगा:

पढ़ने योग्य कोड लिखने और प्रदर्शन करने वाले कोड लिखने के बीच लगभग हमेशा एक व्यापार होता है।

यह वास्तव में भ्रामक बयान है, और एक संभावित खतरनाक रवैया है। कुछ विशिष्ट मामले हैं जहां आपको एक व्यापार करना पड़ता है, लेकिन सामान्य तौर पर दो कारक स्वतंत्र होते हैं।

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

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

(पायथन जैसी कुछ गतिशील भाषाओं में एक महत्वपूर्ण तरीका-कॉल ओवरहेड है। लेकिन यदि प्रदर्शन एक मुद्दा बन जाता है, तो शायद आपको पहले स्थान पर पायथन का उपयोग नहीं करना चाहिए।)

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


5

अधिकांश मामलों में फ़ंक्शन कॉल ओवरहेड महत्वहीन है।

हालाँकि inlining कोड से बड़ा लाभ नए कोड को इनलाइन करने के बाद अनुकूलित करना है

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

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


5

मान लेना प्रदर्शन आपके कार्यक्रम के लिए मायने रखता है, और इसमें वास्तव में बहुत सारे और बहुत सारे कॉल हैं, कॉल के प्रकार के आधार पर लागत अभी भी हो सकती है या नहीं भी हो सकती है।

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

OTOH, फ़ंक्शन को कॉल करने के लिए एक गैर-स्पष्ट लागत है: कॉल से पहले और बाद में उनका मात्र अस्तित्व कंपाइल ऑप्टिमाइज़ेशन को रोक सकता है।

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

उदाहरण के लिए, यदि आप अनावश्यक रूप से प्रत्येक लूप पुनरावृत्ति में एक फ़ंक्शन कहते हैं:

for(int i=0; i < /* gasp! */ strlen(s); i++) x ^= s[i];

संकलक को पता चल सकता है कि यह एक शुद्ध कार्य है, और इसे लूप से बाहर ले जाएं (इस तरह के एक भयानक मामले में भी आकस्मिक O (n ^ 2) एल्गोरिदम को O (n) होना चाहिए):

for(int i=0, end=strlen(s); i < end; i++) x ^= s[i];

और फिर शायद व्यापक / SIMD निर्देशों का उपयोग करके एक बार में 4/8/16 तत्वों को संसाधित करने के लिए लूप को फिर से लिखना।

लेकिन अगर आप लूप में कुछ अपारदर्शी कोड के लिए एक कॉल जोड़ते हैं, भले ही कॉल कुछ भी नहीं करता है और सुपर सस्ता ही है, तो कंपाइलर को सबसे खराब मानना ​​पड़ता है - यह कॉल वैश्विक वैरिएबल को एक्सेस करेगा जो sबदलाव के रूप में उसी मेमोरी को इंगित करता है इसकी सामग्री (भले ही यह constआपके फ़ंक्शन में हो, यह गैर- constकहीं भी हो सकती है), अनुकूलन को असंभव बनाता है:

for(int i=0; i < strlen(s); i++) {
    x ^= s[i];
    do_nothing();
}

3

यह पुराना कागज आपके प्रश्न का उत्तर दे सकता है:

गाइ लेविस स्टील, जूनियर .. "एक्सपेंसिव प्रोसीजर कॉल 'मिथक का विमोचन करते हुए, या, प्रोसीजर कॉल इंप्लीमेंट्स को माना गया हानिकारक, या, लाम्बडा: द अल्टीमेट गोटो"। MIT AI लैब। एआई लैब मेमो एआईएम -443। अक्टूबर 1977।

सार:

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


12
मुझे एक कागज पर बहुत संदेह है कि पुराना इस सवाल का जवाब देगा कि क्या " आधुनिक कॉलरों में फ़ंक्शन कॉल की लागत अभी भी मायने रखती है"।
कोडी ग्रे

6
@CodyGray मुझे लगता है कि संकलक तकनीक को 1977 से आगे बढ़ना चाहिए था। इसलिए यदि 1977 में फ़ंक्शन कॉल को सस्ता किया जा सकता है, तो हमें इसे करने में सक्षम होना चाहिए। तो उत्तर नहीं है। बेशक, यह मानता है कि आप एक सभ्य भाषा कार्यान्वयन का उपयोग कर रहे हैं जो फ़ंक्शन इनलाइनिंग जैसे सामान कर सकते हैं।
एलेक्स वोंग

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

3
जैसा कि दूसरों ने बताया है, यह सिर्फ संकलक तकनीक में बदलाव नहीं है जिन्होंने पुराने शोध को अमान्य कर दिया है। यदि माइक्रोआर्किटेक्चर्स काफी हद तक अपरिवर्तित रहे, तो कंपाइलर्स में सुधार जारी रहा, तो पेपर के निष्कर्ष अभी भी मान्य होंगे। लेकिन ऐसा नहीं हुआ। यदि कुछ भी हो, तो माइक्रोआर्किटेक्टर्स संकलक की तुलना में अधिक बदल गए हैं। चीजें जो पहले तेज हुआ करती थीं अब धीमी गति से, अपेक्षाकृत बोल रही हैं।
कोडी ग्रे

2
@AlexVong सीपीयू परिवर्तनों पर अधिक सटीक होने के लिए जो उस कागज को अप्रचलित बनाता है: 1977 में वापस, एक मुख्य मेमोरी एक्सेस एक एकल सीपीयू चक्र था। आज, यहां तक ​​कि एल 1 (!) कैश की एक सरल पहुंच में 3 से 4 चक्रों की विलंबता है। अब, मेमोरी कॉल्स (स्टैक फ्रेम का निर्माण, रिटर्न एड्रेस की बचत, स्थानीय चर के लिए रजिस्टरों की बचत) में फ़ंक्शन कॉल काफी भारी हैं, जो आसानी से एकल फ़ंक्शन कॉल की लागत को 20 और अधिक चक्रों तक पहुंचाता है। यदि आपका कार्य केवल इसके तर्कों को पुनर्व्यवस्थित करता है, और शायद कॉल-थ्रू पास करने के लिए एक और निरंतर तर्क जोड़ता है, तो यह लगभग 100% ओवरहेड है।
12

3
  • C ++ में डिज़ाइनिंग फ़ंक्शन कॉल की दलीलें जो तर्कों की नकल करती हैं, डिफ़ॉल्ट "पास बाय वैल्यू" है। फंक्शन कॉल ओवरहेड को बचाने के कारण रजिस्टर और अन्य स्टैक-फ्रेम से संबंधित सामान किसी ऑब्जेक्ट की अनपेक्षित (और संभवतः बहुत महंगी) कॉपी से अभिभूत हो सकते हैं।

  • स्टैक-फ्रेम संबंधित अनुकूलन हैं जिन्हें आपको अत्यधिक तथ्य वाले कोड पर देने से पहले जांच करनी चाहिए।

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


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

@MSalters: मुझे लगता है कि आपने गलत तरीके से "विशेष रूप से" "इसके साथ" या कुछ और किया है। प्रतियों या संदर्भों को पारित करने का निर्णय C ++ 11 से पहले था (मुझे पता है कि आप इसे जानते हैं)।
१36 बजे

@phresnel: मुझे लगता है कि मुझे यह सही लगा। मैं जिस विशेष मामले का जिक्र कर रहा हूं वह वह मामला है जहां आप कॉलर में एक अस्थायी निर्माण करते हैं, इसे एक तर्क में स्थानांतरित करते हैं, और फिर इसे कैली में संशोधित करते हैं। C ++ 11 से पहले यह संभव नहीं था, क्योंकि C ++ 03 अस्थायी रूप से गैर-
कॉन्स्टेबल

@MSalters: तब मैंने पहली बार इसे पढ़ने पर आपकी टिप्पणी को गलत समझा। मुझे ऐसा लग रहा था कि आप समझ रहे थे कि C ++ 11 से पहले, मूल्य से गुजरना कुछ ऐसा नहीं था, जो कोई करेगा यदि पारित मूल्य को संशोधित करना चाहेगा।
14

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

3

जैसा कि अन्य कहते हैं, आपको पहले अपने कार्यक्रम के प्रदर्शन को मापना चाहिए, और संभवतः अभ्यास में कोई अंतर नहीं मिलेगा।

फिर भी, एक वैचारिक स्तर से मुझे लगा कि मैं आपके प्रश्न में कुछ चीजें स्पष्ट कर दूंगा। सबसे पहले, आप पूछते हैं:

क्या आधुनिक कॉलरों में फ़ंक्शन कॉल की लागत अभी भी मायने रखती है?

मुख्य शब्द "फ़ंक्शन" और "कंपाइलर" पर ध्यान दें। आपका उद्धरण सबटली अलग है:

याद रखें कि भाषा के आधार पर एक विधि कॉल की लागत महत्वपूर्ण हो सकती है।

यह ऑब्जेक्ट ओरिएंटेड अर्थ में विधियों के बारे में बात कर रहा है।

जब भी "फंक्शन" और "मेथड" को अक्सर परस्पर रूप से उपयोग किया जाता है, तब अंतर होता है जब यह उनकी लागत (जो आप के बारे में पूछ रहे हैं) और जब संकलन की बात आती है (जो आपके द्वारा दिया गया संदर्भ है)।

विशेष रूप से, हमें स्थैतिक प्रेषण बनाम गतिशील प्रेषण के बारे में जानने की आवश्यकता है । मैं इस समय के अनुकूलन को अनदेखा करूँगा।

C जैसी भाषा में, हम आमतौर पर स्थिर प्रेषण के साथ फ़ंक्शन कहते हैं । उदाहरण के लिए:

int foo(int x) {
  return x + 1;
}

int bar(int y) {
  return foo(y);
}

int main() {
  return bar(42);
}

जब कंपाइलर कॉल देखता है foo(y), तो यह जानता है कि उस फ़ंक्शन को किस fooनाम से संदर्भित किया जाता है, इसलिए आउटपुट प्रोग्राम सीधे fooफ़ंक्शन पर कूद सकता है , जो काफी सस्ता है। यही स्टेटिक प्रेषण का मतलब है।

वैकल्पिक डायनेमिक प्रेषण है , जहां संकलक को पता नहीं है कि किस फ़ंक्शन को कहा जा रहा है। एक उदाहरण के रूप में, यहां कुछ हास्केल कोड (सी समतुल्य गड़बड़ होगा!)।

foo x = x + 1

bar f x = f x

main = print (bar foo 42)

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

उन दोनों उदाहरण कार्यों के लिए हैं । आपने उन विधियों का उल्लेख किया है , जिन्हें गतिशील रूप से भेजे जाने वाले कार्य की एक विशेष शैली के रूप में सोचा जा सकता है। उदाहरण के लिए, यहाँ कुछ पायथन है:

class A:
  def __init__(self, x):
    self.x = x

  def foo(self):
    return self.x + 1

def bar(y):
  return y.foo()

z = A(42)
bar(z)

y.foo()कॉल, गतिशील प्रेषण का उपयोग करता है, क्योंकि यह का मान प्राप्त करने के लिए देख रहा है fooमें संपत्ति yवस्तु, और बुला जो कुछ भी यह पाता है; यह पता नहीं है कि yकक्षा होगी A, या कि Aकक्षा में एक fooविधि शामिल है , इसलिए हम सीधे इसे कूद नहीं सकते हैं।

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

तो यह आधुनिक, अनुकूली संकलनकर्ताओं को कैसे प्रभावित करता है?

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

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

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

यदि हमारी भाषा हमें अधिक प्रतिबंध लगाने की अनुमति देती है, उदाहरण के लिए एक एनोटेशन का उपयोग करके yकक्षा तक सीमित Aकरके, तो हम उस जानकारी का उपयोग लक्ष्य फ़ंक्शन का अनुमान लगाने के लिए कर सकते हैं। उपवर्गों वाली भाषाओं में (जो लगभग सभी भाषाओं की कक्षाएं हैं!) जो वास्तव में पर्याप्त नहीं है, क्योंकि yवास्तव में एक अलग (उप) वर्ग हो सकता है, इसलिए हमें जावा की finalएनोटेशन जैसी अतिरिक्त जानकारी की आवश्यकता होगी ताकि वास्तव में पता चल सके कि किस फ़ंक्शन को कहा जाएगा।

हास्केल एक OO भाषा नहीं है, लेकिन हम का मूल्य अनुमान लगा सकते हैं fइनलाइन करने से bar(जो स्थिर में भेजा) main, प्रतिस्थापन fooके लिए y। चूंकि का लक्ष्य fooमें mainस्थिर जाना जाता है, कॉल स्थिर भेजा हो जाता है, और शायद inlined हो जाएगा और पूरी तरह से दूर अनुकूलित (के बाद से इन कार्यों छोटे हैं, संकलक अधिक उन्हें इनलाइन होने की संभावना है, हालांकि हम चाहते हैं कि सामान्य रूप में पर भरोसा नहीं कर सकते )।

इसलिए लागत नीचे आती है:

  • क्या भाषा आपके कॉल को वैधानिक या गतिशील रूप से प्रेषण करती है?
  • यदि यह बाद का है, तो क्या भाषा अन्य जानकारी (जैसे प्रकार, वर्ग, एनोटेशन, इनलाइनिंग, आदि) का उपयोग करके लक्ष्य को अनुमान लगाने की अनुमति देती है?
  • स्थैतिक रूप से प्रेषण (अनुमान या अन्यथा) को कैसे आक्रामक रूप से अनुकूलित किया जा सकता है?

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


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

2

हाँ, एक मिस्ड ब्रांच की भविष्यवाणी आधुनिक हार्डवेयर की तुलना में दशकों पहले की तुलना में अधिक महंगी है, लेकिन कंपाइलरों ने इस पर आशा जताई है।

एक उदाहरण के रूप में, जावा पर विचार करें। पहली नज़र में, फ़ंक्शन कॉल ओवरहेड इस भाषा में विशेष रूप से प्रभावी होना चाहिए:

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

इन प्रथाओं से भयभीत, औसत सी प्रोग्रामर भविष्यवाणी करेगा कि जावा को सी की तुलना में कम से कम एक परिमाण का क्रम होना चाहिए और 20 साल पहले वह सही था। आधुनिक बेंचमार्क हालांकि समान सी कोड के कुछ प्रतिशत के भीतर मुहावरेदार जावा कोड रखता है। वो कैसे संभव है?

एक कारण यह है कि आधुनिक जेवीएम इनलाइन फ़ंक्शन कॉल को पाठ्यक्रम का विषय कहते हैं। यह सट्टा इनलाइनिंग का उपयोग करता है:

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

वह है, कोड:

int x = point.getX();

को फिर से लिखा जाता है

if (point.class != Point) GOTO interpreter;
x = point.x;

और निश्चित रूप से रनटाइम इस प्रकार के चेक को स्थानांतरित करने के लिए पर्याप्त स्मार्ट है, जब तक कि बिंदु असाइन नहीं किया जाता है, या इसे तब तक elide करें जब प्रकार कॉलिंग कोड के लिए जाना जाता है।

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


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

2
मुझे एक ओएस बताएं जो Google Chrome को चलाने से रोकता है "क्योंकि सुरक्षा" (V8 रनटाइम में जावास्क्रिप्ट को मूल कोड में संकलित करता है)। इसके अलावा, इनलाइन एओटी चाहना एक अंतर्निहित कारण नहीं है (यह भाषा द्वारा निर्धारित नहीं किया जाता है, लेकिन आर्किटेक्चर जिसे आप अपने कंपाइलर के लिए चुनते हैं), और जबकि गतिशील लिंकिंग एओटी को संकलन इकाइयों के पार रोकती है, यह संकलन के भीतर इनलाइन को रोकती नहीं है। इकाइयाँ, जहाँ अधिकांश कॉल होते हैं। वास्तव में, उपयोगी इनलाइनिंग यकीनन ऐसी भाषा में आसान है जो जावा की तुलना में डायनेमिक लिंकिंग का उपयोग अत्यधिक कम करती है।
मेरिटॉन

4
विशेष रूप से, iOS गैर-विशेषाधिकार प्राप्त ऐप्स के लिए JIT को रोकता है। क्रोम या फ़ायरफ़ॉक्स को अपने स्वयं के इंजन के बजाय ऐप्पल द्वारा प्रदान किए गए वेब दृश्य का उपयोग करना होगा। अच्छी बात यह है कि एओटी बनाम जेआईटी एक कार्यान्वयन-स्तर है, न कि भाषा-स्तर की पसंद।
आमोन

@meriton विंडोज 10 एस और वीडियो गेम कंसोल ऑपरेटिंग सिस्टम भी थर्ड-पार्टी JIT इंजन को ब्लॉक करते हैं।
डेमियन यरिक

2

याद रखें कि भाषा के आधार पर एक विधि कॉल की लागत महत्वपूर्ण हो सकती है। पढ़ने योग्य कोड लिखने और प्रदर्शन करने वाले कोड लिखने के बीच लगभग हमेशा एक व्यापार होता है।

यह दुर्भाग्य से, अत्यधिक निर्भर है:

  • संकलक टूलचैन, जेआईटी सहित यदि कोई हो,
  • डोमेन।

सबसे पहले, प्रदर्शन अनुकूलन का पहला कानून पहले प्रोफ़ाइल है । कई डोमेन हैं जहां सॉफ्टवेयर भाग का प्रदर्शन पूरे स्टैक के प्रदर्शन के लिए अप्रासंगिक है: डेटाबेस कॉल, नेटवर्क संचालन, ओएस, ...

इसका मतलब यह है कि सॉफ्टवेयर का प्रदर्शन पूरी तरह से अप्रासंगिक है, भले ही यह विलंबता में सुधार नहीं करता है, सॉफ्टवेयर के अनुकूलन से ऊर्जा बचत और हार्डवेयर बचत (या मोबाइल एप्लिकेशन के लिए बैटरी बचत) हो सकती है, जो कि मायने रखती है।

हालाँकि, आमतौर पर उन पर नज़र नहीं रखी जा सकती है, और कई बार एल्गोरिथम में बड़े अंतर से ट्रम्प माइक्रो-ऑप्टिमाइज़ेशन में सुधार होता है।

इसलिए, अनुकूलन करने से पहले, आपको यह समझने की आवश्यकता है कि आप किस चीज के लिए अनुकूलन कर रहे हैं ... और क्या यह इसके लायक है।


अब, शुद्ध सॉफ्टवेयर प्रदर्शन के संबंध में, यह टूलचिन्स के बीच बहुत भिन्न होता है।

फ़ंक्शन कॉल के लिए दो लागतें हैं:

  • रन समय लागत,
  • संकलन समय लागत।

रन समय लागत बल्कि स्पष्ट है; किसी कार्य को करने के लिए एक निश्चित कार्य करना आवश्यक है। उदाहरण के लिए x86 पर C का उपयोग करते हुए, एक फ़ंक्शन कॉल के लिए (1) स्टैक में रजिस्टरों को स्पिलिंग करना होगा, (2) रजिस्टरों के लिए तर्कों को धक्का देना, कॉल का प्रदर्शन करना, और बाद में (3) स्टैक से रजिस्टरों को पुनर्स्थापित करना। इसमें शामिल कार्यों को देखने के लिए कॉलिंग सम्मेलनों का सारांश देखें ।

यह रजिस्टर स्पिलिंग / रीस्टोरेशन एक गैर-तुच्छ राशि है (दर्जनों सीपीयू साइकिल)।

आमतौर पर यह उम्मीद की जाती है कि यह लागत फ़ंक्शन निष्पादित करने की वास्तविक लागत की तुलना में तुच्छ होगी, हालांकि कुछ पैटर्न यहां काउंटर-उत्पादक हैं: गेटर्स, फ़ंक्शन एक साधारण स्थिति द्वारा संरक्षित, आदि ...

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

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

एक विशिष्ट उदाहरण है:

void func(condition: boolean) {
    if (condition) {
        doLotsOfWork();
    }
}

void call() { func(false); }

यदि funcइनबिल्ट है, तो ऑप्टिमाइज़र को एहसास होगा कि ब्रांच को कभी नहीं लिया गया है, और ऑप्टिमाइज़ callकरना है void call() {}

उस अर्थ में, फंक्शन कॉल, ऑप्टिमाइज़र से जानकारी छिपाकर (यदि अभी तक इनलेट नहीं है), कुछ ऑप्टिमाइज़ेशन को बाधित कर सकता है। वर्चुअल फ़ंक्शन कॉल विशेष रूप से इसके लिए दोषी हैं, क्योंकि विचलन (यह साबित करना कि कौन सा फ़ंक्शन अंततः रन टाइम में कहा जाता है) हमेशा आसान नहीं होता है।


अंत में, मेरी सलाह है कि आप पहले स्पष्ट रूप से लिख लें , समय से पहले के एल्गोरिथ्म निराशावाद (क्यूबिक जटिलता या जल्दी से खराब काटने) से बचें, और उसके बाद ही अनुकूलन की आवश्यकता होती है।


1

"याद रखें कि भाषा के आधार पर एक विधि कॉल की लागत महत्वपूर्ण हो सकती है। पढ़ने योग्य कोड लिखने और प्रदर्शनकारी कोड लिखने के बीच लगभग हमेशा एक व्यापार होता है।"

किन परिस्थितियों में यह कथन अभी भी मान्य है कि आजकल के प्रदर्शनकारी आधुनिक संकलकों का समृद्ध उद्योग है?

मैं बस कह रहा हूँ फ्लैट बाहर कभी नहीं कहते हैं। मेरा मानना ​​है कि बोली सिर्फ बाहर फेंकने के लिए लापरवाह होना चाहिए।

बेशक मैं पूरा सच नहीं बोल रहा हूँ, लेकिन मुझे इस बात से कोई फर्क नहीं पड़ता कि वह सच्चा है। यह उस मैट्रिक्स फिल्म की तरह है, मैं भूल गया था अगर यह 1 या 2 या 3 था - मुझे लगता है कि यह बड़े खरबूजे के साथ सेक्सी इतालवी अभिनेत्री के साथ एक था (मुझे वास्तव में कोई पसंद नहीं था लेकिन पहले वाला), जब ओरेकल लेडी ने कीनू रीव्स को बताया, "मैंने अभी आपको बताया कि आपको क्या सुनना चाहिए," या इस प्रभाव के लिए कुछ, जो मैं अभी करना चाहती हूं।

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

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

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

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

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