सब कुछ इनलाइन करने के लिए मजबूर क्यों नहीं करते? [बन्द है]


13

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

तो मेरा सवाल है, क्यों सब कुछ इनलाइन संकलक नहीं है? मुझे लगता है कि यह निष्पादन को उल्लेखनीय रूप से तेज कर देगा।

एकमात्र कारण जिसके बारे में मैं सोच सकता हूं, वह काफी बड़ा निष्पादन योग्य है, लेकिन क्या यह वास्तव में इन दिनों सैकड़ों जीबी मेमोरी के साथ मायने रखता है? क्या बेहतर प्रदर्शन इसके लायक नहीं है?

क्या कोई अन्य कारण है कि कंपाइलर केवल सभी फ़ंक्शन कॉल को इनलाइन नहीं करते हैं?


18
आईडीके आपके बारे में, लेकिन मेरे पास सैकड़ों जीबी मेमोरी नहीं है।
Ampt

2
Isn't the improved performance worth it?एक विधि के लिए जो 100 बार लूप चलाएगा और कुछ गंभीर संख्याओं को क्रंच करेगा, सीपीयू रजिस्टरों में 2 या 3 तर्कों को स्थानांतरित करने का ओवरहेड कुछ भी नहीं है।
डोभाल

5
आप सामान्य रूप से सामान्य हैं, "कंपाइलर्स" का अर्थ "सभी कंपाइलर्स" है और क्या "सब कुछ" का अर्थ वास्तव में "सब कुछ" है? फिर जवाब आसान है, ऐसी स्थितियां हैं जहां आप केवल इनलाइन नहीं कर सकते हैं। मन में पुनरावृत्ति आ जाती है।
ओट्टोवियो डेसिओ

17
कैश फंक्शन एक तरह से छोटे फंक्शन कॉल ओवरहेड की तुलना में अधिक महत्वपूर्ण है।
एसके-तर्क

3
क्या प्रदर्शन सुधार वास्तव में इन दिनों सैकड़ों शक्ति के GFLOPS के साथ होता है?
मौविसील

जवाबों:


22

पहले ध्यान दें कि इनलाइन का एक प्रमुख प्रभाव यह है कि यह कॉल साइट पर और अधिक अनुकूलन करने की अनुमति देता है।

आपके प्रश्न के लिए: ऐसी चीजें हैं जो कठिन या असंभव इनलाइन हैं:

  • गतिशील रूप से जुड़े पुस्तकालय

  • गतिशील रूप से निर्धारित कार्य (डायनामिक डिस्पैच, फंक्शन पॉइंटर्स के माध्यम से कहा जाता है)

  • पुनरावर्ती कार्य (पूंछ पुनरावृत्ति कर सकते हैं)

  • वे कार्य जिनके लिए आपके पास कोड नहीं है (लेकिन लिंक समय अनुकूलन इनमें से कुछ के लिए अनुमति देता है)

तब इनलाइनिंग का न केवल लाभकारी प्रभाव होता है:

  • बड़ा निष्पादन योग्य का अर्थ है अधिक डिस्क स्थान और बड़ा लोड समय

  • बड़े निष्पादन योग्य का मतलब है कैश प्रेशर का बढ़ना (ध्यान दें कि छोटे पर्याप्त कार्य जैसे साधारण गेटर्स को निष्पादित करना निष्पादन योग्य आकार और कैशे को कम कर सकता है)

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


3
कुछ पुनरावर्ती कॉल इनलाइन (टेल कॉल) हो सकते हैं, लेकिन यदि आप वैकल्पिक रूप से एक स्पष्ट स्टैक जोड़ते हैं तो सभी इसे पुनरावृत्ति में बदल सकते हैं
शाफ़्ट फ्रीक

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

11

एक प्रमुख सीमा रनटाइम बहुरूपता है। यदि आप लिखते समय एक गतिशील प्रेषण हो रहा है foo.bar()तो विधि कॉल को इनलाइन करना असंभव है। यह बताता है कि कंपाइलर सब कुछ इनलाइन क्यों नहीं करते हैं।

पुनरावर्ती कॉल को आसानी से इनलाइन नहीं किया जा सकता है।

क्रॉस मॉड्यूल इनलाइनिंग तकनीकी कारणों से बढ़ाना भी मुश्किल है (वृद्धिशील पुनर्संयोजन पूर्व के लिए असंभव होगा)

हालांकि, कंपाइलर बहुत सारी चीजों को इनलाइन करते हैं।


3
वर्चुअल डिस्पैच के माध्यम से इनलाइन करना बहुत मुश्किल है, लेकिन असंभव नहीं है। कुछ सी ++ संकलक कुछ परिस्थितियों में इसे करने में सक्षम हैं।
बस्तमौर

2
... साथ ही कुछ जेआईटी कंपाइलर्स (डिवर्टलाइजेशन)।
फ्रैंक

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

9

सबसे पहले, आप हमेशा इनलाइन नहीं कर सकते हैं, उदाहरण के लिए पुनरावर्ती कार्य हमेशा अयोग्य नहीं हो सकते हैं (लेकिन एक कार्यक्रम जिसमें पुनरावर्ती परिभाषा होती है, factजिसमें केवल एक मुद्रण की आवश्यकता fact(8)हो सकती है)।

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

अंत में, पूर्ण inlining के लिए एक संपूर्ण कार्यक्रम विश्लेषण की आवश्यकता होती है। यह संभव नहीं हो सकता है (या बहुत महंगा है)। GCC द्वारा संकलित C या C ++ के साथ (और Clang / LLVM के साथ भी ) आपको लिंक-टाइम ऑप्टिमाइज़ेशन (उदाहरण के लिए संकलन और लिंक करके ) को सक्षम करने की आवश्यकता है g++ -flto -O2और यह संकलन का काफी समय लेता है।


1
रिकॉर्ड के लिए, LLVM / Clang (और कई अन्य कंपाइलर) भी लिंक-टाइम ऑप्टिमाइज़ेशन का समर्थन करता है
आप

मुझे पता है; LTO पिछली सदी में मौजूद था (IIRC, कुछ MIPS मालिकाना संकलक में कम से कम)।
बेसिल स्टारीनेवविच

7

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

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


यह केवल एक बार किए गए दोहराए गए बिंदुओं को समझाता है और एक पूर्व उत्तर में समझाया गया है
gnat

1
क्या कैश? एल 1? एल 2? L3? कौन सा अधिक महत्वपूर्ण है?
पीटर मोर्टेंसन

1

सब कुछ सम्मिलित करने का मतलब सिर्फ डिस्क मेमोरी की खपत में वृद्धि नहीं होगी, बल्कि आंतरिक मेमोरी की खपत में वृद्धि होगी जो कि भरपूर नहीं है। याद रखें कि कोड कोड में मेमोरी में भी निर्भर करता है; यदि किसी फ़ंक्शन को 10000 स्थानों से कहा जाता है (काफी बड़ी परियोजना में मानक पुस्तकालयों से कहते हैं), तो उस फ़ंक्शन के लिए कोड 10000 गुना अधिक आंतरिक मेमोरी रखता है।

एक और कारण जेआईटी संकलक हो सकता है; अगर सब कुछ इनलाइन है तो गतिशील रूप से संकलित होने के लिए हॉट स्पॉट नहीं हैं।


1

एक, ऐसे सरल उदाहरण हैं जहां सब कुछ पूरी तरह से खराब हो जाएगा। इस सरल सी कोड पर विचार करें:

void f1 (void) { printf ("Hello, world\n"); }
void f2 (void) { f1 (); f1 (); f1 (); f1 (); }
void f3 (void) { f2 (); f2 (); f2 (); f2 (); }
...
void f99 (void) { f98 (); f98 (); f98 (); f98 (); }

लगता है कि सब कुछ आप को क्या करना होगा।

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

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

Btw। मेरे पास "सैकड़ों जीबी मेमोरी नहीं है"। मेरे काम के कंप्यूटर में "हार्ड ड्राइव स्पेस के सैकड़ों GB" भी नहीं हैं। और अगर मेरा आवेदन जहां "सैकड़ों जीबी मेमोरी" है, तो एप्लिकेशन को मेमोरी में लोड होने में 20 मिनट का समय लगेगा।

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