मेमने को सादे कार्यों की तुलना में संकलक द्वारा बेहतर क्यों चुना जा सकता है?


171

उनकी किताब में The C++ Standard Library (Second Edition)निकोलई जोसुटिस ने कहा है कि मेमने को सादे कार्यों की तुलना में बेहतर तरीके से संकलक द्वारा अनुकूलित किया जा सकता है।

इसके अलावा, C ++ कंपाइलर लैंबडास को बेहतर करते हैं, क्योंकि वे साधारण कार्य करते हैं। (पेज पेज 3)

ऐसा क्यों है?

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



मूल रूप से, यह कथन सभी कार्य वस्तुओं पर लागू होता है , न कि केवल लंबदा पर।
newacct

4
यह गलत होगा क्योंकि फंक्शन पॉइंट्स फंक्शन ऑब्जेक्ट्स भी होते हैं।
जोहान्स स्काउब -

2
@litb: मुझे लगता है कि मैं इससे असहमत हूं। ^ W ^ W ^ W ^ W ^ W ^ W (मानक को देखने के बाद) मुझे उस C ++ ism के बारे में जानकारी नहीं थी, हालांकि मुझे लगता है कि आम समानता है (और उसके अनुसार) विकिपीडिया), लोगों का मतलब है कि फंक्शन ऑब्जेक्ट कहने पर उदाहरण-कुछ-कॉल करने योग्य-क्लास।
सेबेस्टियन मच

1
कुछ compilers सादा कार्यों से अनुकूलन lambdas बेहतर कर सकते हैं, लेकिन सभी नहीं :-(
कोड़ी ग्रे

जवाबों:


175

कारण यह है कि लंबोदर फ़ंक्शन ऑब्जेक्ट्स हैं इसलिए उन्हें फंक्शन टेम्प्लेट में पास करने से उस ऑब्जेक्ट के लिए विशेष रूप से एक नया फ़ंक्शन इंस्टेंट हो जाएगा। कंपाइलर इस प्रकार तुच्छ रूप से लैम्ब्डा कॉल को इनलाइन कर सकता है।

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

एक उदाहरण के रूप में, निम्न फ़ंक्शन टेम्पलेट पर विचार करें:

template <typename Iter, typename F>
void map(Iter begin, Iter end, F f) {
    for (; begin != end; ++begin)
        *begin = f(*begin);
}

इसे लंबोदर के साथ इस तरह बुलाना:

int a[] = { 1, 2, 3, 4 };
map(begin(a), end(a), [](int n) { return n * 2; });

इस तात्कालिकता में परिणाम (कंपाइलर द्वारा निर्मित):

template <>
void map<int*, _some_lambda_type>(int* begin, int* end, _some_lambda_type f) {
    for (; begin != end; ++begin)
        *begin = f.operator()(*begin);
}

... संकलक जानता है _some_lambda_type::operator ()और इनलाइन को तुच्छ रूप से कॉल कर सकता है। (और किसी अन्य लैंबडा के mapसाथ फ़ंक्शन को लागू करने से एक नया तात्कालिकता पैदा होगी क्योंकि प्रत्येक लैम्ब्डा का एक अलग प्रकार है।)map

लेकिन जब एक फ़ंक्शन पॉइंटर के साथ बुलाया जाता है, तो इंस्टेंटेशन निम्नानुसार दिखता है:

template <>
void map<int*, int (*)(int)>(int* begin, int* end, int (*f)(int)) {
    for (; begin != end; ++begin)
        *begin = f(*begin);
}

… और यहां fप्रत्येक कॉल के लिए एक अलग पते की ओर इशारा किया गया है mapऔर इस तरह कंपाइलर इनलाइन कॉल नहीं कर सकता है fजब तक कि आसपास के कॉल को mapभी इनलेट नहीं किया गया है ताकि कंपाइलर fएक विशिष्ट फ़ंक्शन को हल कर सके।


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

2
@ पूरी तरह से। समस्या तब होती है जब ऐसे कार्यों को संभालना जिन्हें इनलाइन नहीं किया जा सकता है (क्योंकि वे बहुत बड़े हैं)। यहां कॉलबैक पर कॉल अभी भी लंबोदर के मामले में इनलाइन की जा सकती है, लेकिन फ़ंक्शन पॉइंटर के मामले में नहीं। std::sortइसका एक शास्त्रीय उदाहरण यह है कि यहां फ़ंक्शन पॉइंटर के बजाय लैम्ब्डा का उपयोग सात-गुना (शायद अधिक, लेकिन मेरे पास उस पर कोई डेटा नहीं है) प्रदर्शन बढ़ता है।
कोनराड रुडोल्फ

1
@greggo आप दो कार्यों यहाँ भ्रमित कर रहे हैं: एक हम लैम्ब्डा से गुजर रहे हैं करने के लिए (उदाहरण के लिए std::sort, या mapमेरे उदाहरण में) और लैम्ब्डा ही। लंबोदर आमतौर पर छोटा होता है। अन्य कार्य - जरूरी नहीं है। हम अन्य फ़ंक्शन के अंदर लैम्बडा को इनलाइनिंग कॉल से चिंतित हैं ।
कोनराड रुडोल्फ

2
@ मुझे पता है। यह वस्तुतः मेरे उत्तर का अंतिम वाक्य है, हालांकि।
कोनराड रुडोल्फ

1
जो मुझे उत्सुक लगता है (बस उस पर ठोकर खाई है) वह यह है कि एक साधारण बूलियन फ़ंक्शन दिया गया है predजिसकी परिभाषा दिखाई देती है, और gcc v5.3 का उपयोग करते हुए, std::find_if(b, e, pred)इनलाइन नहीं करता है pred, लेकिन std::find_if(b, e, [](int x){return pred(x);})करता है। क्लैंग दोनों को इनलाइन करता है, लेकिन लैम्ब्डा के साथ g ++ की तरह तेजी से कोड का उत्पादन नहीं करता है।
रिची

26

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


5
"लैम्बडा फ़ंक्शन के लिए कॉल एक प्रत्यक्ष कॉल है" - वास्तव में। और यही बात सभी कार्यों की वस्तुओं के लिए सही है , न कि केवल लंबोदर के लिए। यह सिर्फ फंक्शन पॉइंटर्स हैं जो आसानी से इनबिल्ट नहीं हो सकते हैं, अगर बिल्कुल भी।
पीट बेकर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.