दिलचस्प पोस्ट
एंड्रॉइड सूचीदृश्य पंक्ति हटाने एनीमेशन डेल्फी भाषा सुविधाओं और संस्करण की सूची जिसमें वे शामिल / बहिष्कृत किए गए थे Windows.Forms.Timer या सिस्टम। थ्रेडिंग। टाइमर LINQ के साथ आप "नॉट इन" क्वेरी कैसे करेंगे? कैसे नोडजे का उपयोग कर एक छवि की सेवा अब जावा वेब फ्रेमवर्क का चयन करना है? ब्राउज़रों में मूसीव्हील की गति को सामान्य करना PHP के साथ दो छवियों को मर्ज करना अपने गुणों के आधार पर सरणी के तत्व का सूचकांक प्राप्त करना क्या मैं <a href…> में एम्पर्संस को सांकेतिकृत करता हूं? PHP से जावास्क्रिप्ट में चर प्राप्त करें कब है। (सफलता, असफल) वादों के लिए एक विरोधी माना जाता है? मैं "प्रकार त्रुटि" क्यों देख रहा हूं: स्ट्रिंग इंडेक्स पूर्णांक होना चाहिए "? असाइनमेंट ऑपरेटर को ले जाएं और `यदि (यह! = & Rhs)` iPhone 6 प्लस संकल्प भ्रम: Xcode या ऐप्पल की वेबसाइट? विकास के लिए

std :: फ़ंक्शन बनाम टेम्पलेट

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

 #include <iostream> #include <functional> #include <string> #include <chrono> template <typename F> float calc1(F f) { return -1.0f * f(3.3f) + 666.0f; } float calc2(std::function<float(float)> f) { return -1.0f * f(3.3f) + 666.0f; } int main() { using namespace std::chrono; const auto tp1 = system_clock::now(); for (int i = 0; i < 1e8; ++i) { calc1([](float arg){ return arg * 0.5f; }); } const auto tp2 = high_resolution_clock::now(); const auto d = duration_cast<milliseconds>(tp2 - tp1); std::cout << d.count() << std::endl; return 0; } 

111 एमएस बनाम 1241 एमएस मैं मानता हूं क्योंकि टेम्पलेट अच्छी तरह से रेखांकित किया जा सकता है, जबकि function आभासी कॉल के माध्यम से आंतरिक को कवर करता है।

जाहिर तौर पर टेम्पलेट्स के पास उनके मुद्दे हैं जैसे मैं उन्हें देखता हूं:

  • उन्हें हेडर्स के रूप में उपलब्ध कराया जाना चाहिए जो आपके द्वारा बंद किए गए कोड के रूप में अपनी लाइब्रेरी को रिलीज़ करने के दौरान कुछ नहीं करना चाहेगा,
  • वे संकलन का समय अधिक समय तक बना सकते हैं जब तक कि extern template जैसी नीति लागू नहीं की जाती है,
  • एक टेम्पलेट की आवश्यकताओं (अवधारणाओं, किसी को भी?) का प्रतिनिधित्व करने के लिए कोई भी (कम से कम मुझे जाना जाता है) साफ तरीका नहीं है, यह बताता है कि किस प्रकार के मज़ेदार की उम्मीद है

क्या मैं इस प्रकार मान सकता हूँ कि function एस का उपयोग कर फैक्टरों को पारित करने के वास्तविक मानक के रूप में इस्तेमाल किया जा सकता है, और उन स्थानों में जहां उच्च निष्पादन की उम्मीद है टेम्पलेट्स का उपयोग किया जाना चाहिए?


संपादित करें:

मेरे कंपाइलर सीटीपी के बिना विजुअल स्टूडियो 2012 है।

वेब के समाधान से एकत्रित समाधान "std :: फ़ंक्शन बनाम टेम्पलेट"

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

सामान्य तौर पर, टेम्पलेट्स का विकल्प सिर्फ एक व्यापक सिद्धांत का एक उदाहरण है: संकलन-समय पर जितनी संभव हो उतनी बाधाओं को निर्दिष्ट करने का प्रयास करें । तर्क सरल है: यदि आप अपने प्रोग्राम से पहले ही एक त्रुटि, या एक प्रकार की बेमेल पकड़ सकते हैं, तो आप अपने ग्राहक के लिए एक छोटी गाड़ी कार्यक्रम नहीं भेजेंगे।

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

हां, यह सच है कि टेम्पलेट समर्थन सही नहीं है, और सी ++ 11 अभी भी अवधारणाओं के लिए समर्थन की कमी है; हालांकि, मुझे नहीं लगता कि std::function आपको उस संबंध में कैसे बचाएगा I std::function टेम्प्लेट का एक विकल्प नहीं है, बल्कि डिजाइन स्थितियों के लिए एक उपकरण है जहां टेम्प्लेट का उपयोग नहीं किया जा सकता है।

ऐसे उपयोग का एक मामला तब उठता है, जब आपको कॉल करने योग्य ऑब्जेक्ट को एक विशिष्ट हस्ताक्षर का पालन करते हुए रन-टाइम पर एक कॉल को हल करने की आवश्यकता होती है, लेकिन संकलित समय में इसकी कंक्रीट प्रकार अज्ञात होती है। यह आम तौर पर ऐसा मामला है जब आपके पास संभवतः विभिन्न प्रकार के कॉलबैक का संग्रह होता है, लेकिन जिसे आपको समान रूप से लागू करने की आवश्यकता होती है; पंजीकृत कॉलबैक का प्रकार और संख्या आपके कार्यक्रम की स्थिति और आवेदन तर्क के आधार पर रन-टाइम पर निर्धारित की जाती है। इनमें से कुछ कॉलबैक फ़ैक्टर्स हो सकते हैं, कुछ सादे कार्य हो सकते हैं, कुछ अन्य तर्कों के लिए कुछ तर्कों को बाध्य करने का नतीजा हो सकता है।

std::function और std::bind भी C ++ में कार्यात्मक प्रोग्रामिंग को सक्षम करने के लिए एक प्राकृतिक मुहावरे प्रदान करते हैं, जहां फ़ंक्शन ऑब्जेक्ट के रूप में माना जाता है और स्वाभाविक रूप से क्युरीड और अन्य फ़ंक्शन जेनरेट करने के लिए मिलते हैं। यद्यपि इस प्रकार का संयोजन टेम्पलेट्स के साथ भी प्राप्त किया जा सकता है, समान रूप से एक समान डिज़ाइन की स्थिति आम तौर पर इस्तेमाल के मामलों के साथ मिलती है, जो रन-टाइम पर संयुक्त कॉलबल ऑब्जेक्ट के प्रकार को निर्धारित करने की आवश्यकता होती है।

अंत में, ऐसी अन्य स्थितियां हैं जहां std::function अपरिहार्य है, उदाहरण के लिए यदि आप रिकर्सिव लैंबडा लिखना चाहते हैं; हालांकि, इन प्रतिबंधों को वैचारिक भेदों की तुलना में तकनीकी सीमाओं के आधार पर और अधिक विश्वास किया जाता है।

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

एंडी पर्वाल ने अच्छी तरह से डिजाइन के मुद्दों को कवर किया है। यह निश्चित रूप से बहुत ही महत्वपूर्ण है, लेकिन मेरा मानना ​​है कि मूल प्रश्न std::function से संबंधित अधिक कार्यक्षमता के मुद्दों को लेकर है।

सबसे पहले, माप तकनीक पर एक त्वरित टिप्पणी: कैल्सा 1 के लिए प्राप्त की गई calc1 का बिल्कुल अर्थ नहीं है। दरअसल, जनित विधानसभा (या विधानसभा कोड डिबगिंग) को देखते हुए, एक यह देख सकता है कि वीएस2012 के अनुकूलक को यह समझने में काफी चतुर है कि कॉलिंग का परिणाम calc1 को स्वतंत्र है और कॉल को लूप से बाहर ले जाता है:

 for (int i = 0; i < 1e8; ++i) { } calc1([](float arg){ return arg * 0.5f; }); 

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

जैसा कि यह बताया गया है, अनुकूलक के पास std::function को समझने में अधिक परेशानी होती है और कॉल को लूप से बाहर नहीं ले जाता है। तो 1241ms कैल्सा 2 के लिए एक उचित माप है।

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

मानक (20.8.11.2.1 / 5) छोटे वस्तुओं के लिए डायनामिक स्मृति आवंटन से बचने के लिए कार्यान्वित करता है, जो शुक्र, VS2012 करता है (विशेष रूप से, मूल कोड के लिए)।

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

 float a, b, c; // never mind the values // ... calc2([a,b,c](float arg){ return arg * 0.5f; }); 

इस संस्करण के लिए, समय लगभग 16000 एमएमएस (मूल कोड के लिए 1241 एमएस की तुलना में) है।

आखिरकार, ध्यान दें कि लैम्ब्डा के जीवनकाल में std::function को std::function । इस मामले में, लैम्ब्डा की एक प्रतिलिपि को संभाल करने की बजाय, std::function इसके लिए "संदर्भ" स्टोर कर सकता है। "संदर्भ" से मेरा मतलब है एक std::reference_wrapper जो आसानी से कार्य std::ref और std::cref । अधिक सटीक, का उपयोग करके:

 auto func = [a,b,c](float arg){ return arg * 0.5f; }; calc2(std::cref(func)); 

समय लगभग 1860 एमएमएस तक घट जाता है।

मैंने कुछ समय पहले लिखा था:

http://www.drdobbs.com/cpp/efficient-use-of-lambda-expressions-and/232500059

जैसा कि मैंने लेख में कहा था, सी ++ 11 के अपने गरीब समर्थन के कारण तर्क VS2010 के लिए काफी लागू नहीं है। लेखन के समय, वीएस2012 का केवल एक बीटा संस्करण उपलब्ध था, लेकिन सी ++ 11 के लिए इसका समर्थन पहले से ही इस मामले के लिए काफी अच्छा था।

रेशम के साथ दो के बीच कोई प्रदर्शन अंतर नहीं है

क्लैग (3.2, ट्रंक 166872) (लिनक्स पर ओओ 2) का उपयोग करते हुए, दो मामलों से बायनेरिज़ वास्तव में समान होते हैं

-मैं पोस्ट के अंत में झुमके में वापस आऊँगा लेकिन सबसे पहले, जीसीसी 4.7.2:

पहले से ही बहुत सारी जानकारी हो रही है, लेकिन मैं कहना चाहता हूं कि कैल्क 1 और कैल्क 2 की गणना के परिणाम समान नहीं हैं, इन-लाइनिंग इत्यादि के कारण। उदाहरण के लिए सभी परिणामों की तुलना करें:

 float result=0; for (int i = 0; i < 1e8; ++i) { result+=calc2([](float arg){ return arg * 0.5f; }); } 

Calc2 के साथ जो हो जाता है

 1.71799e+10, time spent 0.14 sec 

जबकि calc1 के साथ यह हो जाता है

 6.6435e+10, time spent 5.772 sec 

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

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

अगर मैं calc1 और 2 के लिए एक उपयोगकर्ता की आपूर्ति की गई मूल्य तर्क जोड़ता हूँ तो calc1 और calc2 के बीच की गति कारक 40 से 40 के एक कारक के नीचे आता है! दृश्य स्टूडियो के साथ अंतर 2 के एक कारक के करीब है, और रिंग से कोई अंतर नहीं है (नीचे देखें)।

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

बजना:

क्लैग (मैंने 3.2 प्रयोग किया था) जब मैं उदाहरण कोड (नीचे पोस्ट किया गया) के लिए calc1 और calc2 के बीच फ्लिप करता हूँ, तो वास्तव में समान बिनरियों का उत्पादन किया जाता है। प्रश्न में पोस्ट किए गए मूल उदाहरण के साथ दोनों समान हैं, लेकिन सभी पर कोई समय नहीं लेते हैं (ऊपर बताए गए अनुसार छोरों को पूरी तरह से हटा दिया गया है) मेरे संशोधित उदाहरण के साथ -O2 के साथ:

निष्पादित करने के लिए सेकंड की संख्या (सर्वोत्तम 3):

 clang: calc1: 1.4 seconds clang: calc2: 1.4 seconds (identical binary) gcc 4.7.2: calc1: 1.1 seconds gcc 4.7.2: calc2: 6.0 seconds VS2012 CTPNov calc1: 0.8 seconds VS2012 CTPNov calc2: 2.0 seconds VS2015 (14.0.23.107) calc1: 1.1 seconds VS2015 (14.0.23.107) calc2: 1.5 seconds MinGW (4.7.2) calc1: 0.9 seconds MinGW (4.7.2) calc2: 20.5 seconds 

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

मेरा संशोधित परीक्षण कोड:

 #include <functional> #include <chrono> #include <iostream> template <typename F> float calc1(F f, float x) { return 1.0f + 0.002*x+f(x*1.223) ; } float calc2(std::function<float(float)> f,float x) { return 1.0f + 0.002*x+f(x*1.223) ; } int main() { using namespace std::chrono; const auto tp1 = high_resolution_clock::now(); float result=0; for (int i = 0; i < 1e8; ++i) { result=calc1([](float arg){ return arg * 0.5f; },result); } const auto tp2 = high_resolution_clock::now(); const auto d = duration_cast<milliseconds>(tp2 - tp1); std::cout << d.count() << std::endl; std::cout << result<< std::endl; return 0; } 

अद्यतन करें:

जोड़ा गया बनाम 201 मैंने यह भी गौर किया है कि calc1, calc2 में डबल-फ्लोट रूपांतरण हैं। उन्हें निकालना दृश्य स्टूडियो के निष्कर्ष को नहीं बदलता है (दोनों बहुत तेजी से हैं लेकिन अनुपात उसी के बारे में है)।

अलग एक ही नहीं है

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

 void eval(const std::function<int(int)>& f) { std::cout << f(3); } int f1(int i) { return i; } float f2(double d) { return d; } int main() { std::function<int(int)> fun(f1); eval(fun); fun = f2; eval(fun); return 0; } 

नोट करें कि एक ही फ़ंक्शन ऑब्जेक्ट, fun , दोनों कॉलों को पारित किया जा रहा है, इसमें दो भिन्न कार्य हैं

यदि आपको ऐसा करने की आवश्यकता नहीं है, तो आपको std::function उपयोग नहीं करना चाहिए।

आपके पास पहले से ही कुछ अच्छे उत्तर हैं, इसलिए मैं उनका विरोध नहीं करने जा रहा हूं, टेम्प्लेट में std :: function की तुलना करने के लिए कम से कम कार्य करने के लिए वर्चुअल फ़ंक्शन की तुलना करना है। फ़ंक्शन पर आपको "पसंद" वर्चुअल फ़ंक्शन को कभी नहीं करना चाहिए, बल्कि आप आभासी फ़ंक्शन का उपयोग करते हैं, जब यह समस्या को ठीक करता है, समय को चलाने के लिए समय संकलन से निर्णय ले जाता है। विचार यह है कि एक bespoke समाधान (जैसे एक कूद तालिका) का उपयोग करते हुए समस्या को हल करने के बजाय आप ऐसा कुछ उपयोग करते हैं जो कंपाइलर को आपके लिए अनुकूलित करने का एक बेहतर मौका देता है। यह अन्य प्रोग्रामर को भी मदद करता है, यदि आप एक मानक समाधान का उपयोग करते हैं

यह उत्तर मौजूदा उत्तरों के सेट में योगदान करना है, जो मुझे std :: function कॉल की रनटाइम लागत के लिए अधिक सार्थक बेंचमार्क मानना ​​है।

स्टडी :: फ़ंक्शन मैकेनिज़्म को यह प्रदान करने के लिए पहचाना जाना चाहिए: कोई भी योग्य इकाई को उपयुक्त हस्ताक्षर के std :: फ़ंक्शन में कनवर्ट किया जा सकता है। मान लीजिए आपके पास लाइब्रेरी है जो z = f (x, y) से परिभाषित फ़ंक्शन के लिए सतह को फिट करती है, आप इसे std::function<double(double,double)> स्वीकार करने के लिए लिख सकते हैं, और पुस्तकालय का उपयोगकर्ता आसानी से किसी भी सुगम इकाई को परिवर्तित; यह एक साधारण कार्य हो, एक क्लास उदाहरण या लैम्ब्डा की कोई विधि या कुछ भी जो std :: bind द्वारा समर्थित है।

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

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

मैंने ओपी के समान परीक्षण किया, लेकिन मुख्य बदलाव हैं:

  1. प्रत्येक मामले 1 बिलियन बार छोर देता है, लेकिन std :: फ़ंक्शन ऑब्जेक्ट केवल एक बार बनाये जाते हैं। मैंने आउटपुट कोड को देखकर पाया है कि 'ऑपरेटर न्यू' को वास्तविक स्टडी :: फ़ंक्शन कॉल्स का निर्माण करते समय कहा जाता है (हो सकता है कि जब वह ऑप्टिमाइज़ न हों)।
  2. अवांछित अनुकूलन को रोकने के लिए परीक्षण दो फ़ाइलों में विभाजित है
  3. मेरे मामले हैं: (ए) फ़ंक्लिल एक सामान्य फ़ंक्शन पॉइंटर (बी) फ़ंक्शन द्वारा पारित किया जाता है फ़ंक्शन एक संगत फ़ंक्शन होता है जिसे std :: function (d) फ़ंक्शन के रूप में लपेटा जाता है फ़ंक्शन एक असंगत फ़ंक्शन है जिसे std :: के साथ संगत किया गया है बाँध, std :: function के रूप में लिपटे

मैं जो परिणाम प्राप्त करता हूं:

  • मामले (ए) (इनलाइन) 1.3 एनएससी

  • अन्य सभी मामलों: 3.3 एनएसईसी।

मामले (डी) थोड़ा धीमा हो जाता है, लेकिन अंतर (लगभग 0.05 एनएससी) शोर में अवशोषित होता है।

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

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

-ओ 2 जीसीसी 4.8.1, एक्स 86_64 लक्ष्य (कोर i5) के लिए

नोट, कोड को दो फाइल्स में विभाजित किया जाता है, ताकि कम्पाइलर को उन कार्यों को विस्तारित करने से रोक दिया जाता है जहां उन्हें कहा जाता है (एक मामले में जहां यह करना है)।

—– पहला स्रोत फ़ाइल ————–

 #include <functional> // simple funct float func_half( float x ) { return x * 0.5; } // func we can bind float mul_by( float x, float scale ) { return x * scale; } // // func to call another func a zillion times. // float test_stdfunc( std::function<float(float)> const & func, int nloops ) { float x = 1.0; float y = 0.0; for(int i =0; i < nloops; i++ ){ y += x; x = func(x); } return y; } // same thing with a function pointer float test_funcptr( float (*func)(float), int nloops ) { float x = 1.0; float y = 0.0; for(int i =0; i < nloops; i++ ){ y += x; x = func(x); } return y; } // same thing with inline function float test_inline( int nloops ) { float x = 1.0; float y = 0.0; for(int i =0; i < nloops; i++ ){ y += x; x = func_half(x); } return y; } 

—– दूसरा स्रोत फ़ाइल ————-

 #include <iostream> #include <functional> #include <chrono> extern float func_half( float x ); extern float mul_by( float x, float scale ); extern float test_inline( int nloops ); extern float test_stdfunc( std::function<float(float)> const & func, int nloops ); extern float test_funcptr( float (*func)(float), int nloops ); int main() { using namespace std::chrono; for(int icase = 0; icase < 4; icase ++ ){ const auto tp1 = system_clock::now(); float result; switch( icase ){ case 0: result = test_inline( 1e9); break; case 1: result = test_funcptr( func_half, 1e9); break; case 2: result = test_stdfunc( func_half, 1e9); break; case 3: result = test_stdfunc( std::bind( mul_by, std::placeholders::_1, 0.5), 1e9); break; } const auto tp2 = high_resolution_clock::now(); const auto d = duration_cast<milliseconds>(tp2 - tp1); std::cout << d.count() << std::endl; std::cout << result<< std::endl; } return 0; } 

दिलचस्पी रखने वालों के लिए, यहाँ अनुकूलक को 'mul_by' को फ्लोट (फ्लोट) की तरह बनाया जाने वाला कम्पाइलर बनाया गया है- इसे 'कॉल' कहा जाता है जब फ़ंक्शन को बाइंड (mul_by, _1,0.5) के रूप में बनाया जाता है:

 movq (%rdi), %rax ; get the std::func data movsd 8(%rax), %xmm1 ; get the bound value (0.5) movq (%rax), %rdx ; get the function to call (mul_by) cvtpd2ps %xmm1, %xmm1 ; convert 0.5 to 0.5f jmp *%rdx ; jump to the func 

(अतः अगर मैं बाइंड में 0.5 एफ लिखेगा तो यह थोड़ा तेज हो सकता है …) ध्यान दें कि 'x' पैरामीटर% xmm0 में आता है और बस वहां रहता है।

यहाँ उस क्षेत्र में कोड है जहां समारोह का निर्माण किया गया है, test_stdfunc को कॉल करने से पहले – c ++ filt के माध्यम से चलाया जाता है:

 movl $16, %edi movq $0, 32(%rsp) call operator new(unsigned long) ; get 16 bytes for std::function movsd .LC0(%rip), %xmm1 ; get 0.5 leaq 16(%rsp), %rdi ; (1st parm to test_stdfunc) movq mul_by(float, float), (%rax) ; store &mul_by in std::function movl $1000000000, %esi ; (2nd parm to test_stdfunc) movsd %xmm1, 8(%rax) ; store 0.5 in std::function movq %rax, 16(%rsp) ; save ptr to allocated mem ;; the next two ops store pointers to generated code related to the std::function. ;; the first one points to the adaptor I showed above. movq std::_Function_handler<float (float), std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_invoke(std::_Any_data const&, float), 40(%rsp) movq std::_Function_base::_Base_manager<std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation), 32(%rsp) call test_stdfunc(std::function<float (float)> const&, int) 

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

 template <typename F> float calc1(F f, float i) { return -1.0f * f(i) + 666.0f; } float calc2(std::function<float(float)> f, float i) { return -1.0f * f(i) + 666.0f; } int main() { const auto tp1 = system_clock::now(); for (int i = 0; i < 1e8; ++i) { t += calc2([&](float arg){ return arg * 0.5f + t; }, i); } const auto tp2 = high_resolution_clock::now(); } 

जीसीसी 4.8-ओ 3 के साथ संकलित कोड में इस बदलाव को देखते हुए और calc1 के लिए 330 सेकंड और calc2 के लिए 2702 का समय मिला। तो टेम्पलेट का इस्तेमाल 8 गुना तेज था, यह संख्या मुझ पर संदिग्धों को देखा गया, 8 की शक्ति की गति अक्सर इंगित करती है कि कंपाइलर ने कुछ वैक्टर किया है जब मैंने टेम्प्लेट संस्करण के लिए जेनरेट किए गए कोड को देखा तो यह स्पष्ट रूप से अजीब हो गया था

 .L34: cvtsi2ss %edx, %xmm0 addl $1, %edx movaps %xmm3, %xmm5 mulss %xmm4, %xmm0 addss %xmm1, %xmm0 subss %xmm0, %xmm5 movaps %xmm5, %xmm0 addss %xmm1, %xmm0 cvtsi2sd %edx, %xmm1 ucomisd %xmm1, %xmm2 ja .L37 movss %xmm0, 16(%rsp) 

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

यह मुझे कुछ और करने की कोशिश करने के लिए प्रेरित किया कि क्या मैं संकलक को std :: function संस्करण पर समान अनुकूलन करने के लिए मिल सकता है। किसी फ़ंक्शन में जाने के बजाय मैं एक वैश्विक वर्णाक्षर के रूप में एक std :: function करता हूं, और इसे कॉल किया है।

 float calc3(float i) { return -1.0f * f2(i) + 666.0f; } std::function<float(float)> f2 = [](float arg){ return arg * 0.5f; }; int main() { const auto tp1 = system_clock::now(); for (int i = 0; i < 1e8; ++i) { t += calc3([&](float arg){ return arg * 0.5f + t; }, i); } const auto tp2 = high_resolution_clock::now(); } 

इस संस्करण के साथ हम देखते हैं कि कंपाइलर ने कोड को वैसे ही वैक्टर किया है और मुझे वही बेंचमार्क परिणाम मिलता है।

  • टेम्पलेट: 330ms
  • std :: फ़ंक्शन: 2702ms
  • वैश्विक स्तर :: कार्य: 330ms

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