2013-09-04 13 views
20

में लैम्ब्डा फ़ंक्शंस के ऊपरी हिस्से को समझना यह Why C++ lambda is slower than ordinary function when called multiple times? और C++0x Lambda overhead में पहले से ही छुआ था, लेकिन मुझे लगता है कि मेरा उदाहरण पूर्व में चर्चा से थोड़ा अलग है और परिणामस्वरूप उत्तरार्द्ध में परिणाम है।सी ++ 11

मेरी कोड में अड़चन के लिए खोज पर मैं एक recusive टेम्पलेट समारोह है कि किसी भी प्रोसेसर समारोह के साथ एक variadic तर्क सूची को संसाधित करता है, एक बफर में मूल्य को कॉपी की तरह मिल गया।

int buffer[10]; 
int main(int argc, char **argv) 
{ 
    int *p = buffer; 

    for (unsigned long int i = 0; i < 10E6; ++i) 
    { 
    p = buffer; 
    ProcessArguments<int>([&p](const int &v) { *p++ = v; }, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
    } 
} 

संकलित:

template <typename T> 
void ProcessArguments(std::function<void(const T &)> process) 
{} 

template <typename T, typename HEAD, typename ... TAIL> 
void ProcessArguments(std::function<void(const T &)> process, const HEAD &head, const TAIL &... tail) 
{ 
    process(head); 
    ProcessArguments(process, tail...); 
} 

मैं एक कार्यक्रम एक लैम्ब्डा समारोह के साथ-साथ एक वैश्विक समारोह है कि एक वैश्विक बफर में प्रतियां तर्क एक चलती सूचक का उपयोग कर के साथ साथ इस कोड का उपयोग करता है के क्रम तुलना जी ++ 4.6 और -O3 उपकरण समय के साथ मापने के साथ अपने मशीन पर अधिक से अधिक 6 सेकंड लेता है, जबकि

int buffer[10]; 
int *p = buffer; 
void CopyIntoBuffer(const int &value) 
{ 
    *p++ = value; 
} 

int main(int argc, char **argv) 
{ 
    int *p = buffer; 

    for (unsigned long int i = 0; i < 10E6; ++i) 
    { 
    p = buffer; 
    ProcessArguments<int>(CopyIntoBuffer, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
    } 

    return 0; 
} 

1,4 के बारे में सेकंड लेता है।

मुझे उन दृश्यों के पीछे क्या नहीं चल रहा है जो समय के ऊपर की व्याख्या करते हैं और मुझे आश्चर्य है कि क्या मैं रनटाइम के भुगतान के बिना लैम्ब्डा कार्यों का उपयोग करने के लिए कुछ बदल सकता हूं।

+0

तो वैश्विक एक वास्तव में धीमी है: ध्यान दें कि यह प्रत्याशित ही inlined लैम्ब्डा शरीर आप सबसे अच्छा प्रदर्शन दे रहा है? आप कहते हैं कि यह लैम्ब्डा-आधारित एक के लिए 6 बनाम 1.4 है, लेकिन फिर अंतिम वाक्य समझ में नहीं आता है। – dasblinkenlight

+0

अपने विश्लेषण करते समय, क्या आपने जेनरेट की गई असेंबली सूची में देखा? – WhozCraig

+0

'const' संदर्भ द्वारा 'प्रक्रिया' को पारित कर देगा, इस तरह,' शून्य प्रक्रिया क्रियाएं (const std :: function और प्रक्रिया) ', कोई फर्क पड़ता है? – dasblinkenlight

उत्तर

33

समस्या यहां std :: function का उपयोग है। आप इसे कॉपी द्वारा भेजते हैं और इसलिए इसकी सामग्री की प्रतिलिपि बनाते हैं (और पैरामीटर को अनदेखा करते समय इसे फिर से कर रहे हैं)।

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

यहां कई विकल्प हैं, और सबसे अच्छा शायद std :: function नहीं, बल्कि टेम्पलेट को पास नहीं करेगा। लाभ यह है कि आपकी विधि कॉल को रेखांकित करने की अधिक संभावना है, std :: function द्वारा कोई प्रकार का मिटा नहीं होता है, कोई प्रतिलिपि नहीं होती है, सब कुछ बहुत अच्छा होता है। कि जैसा:

template <typename TFunc> 
void ProcessArguments(const TFunc& process) 
{} 

template <typename TFunc, typename HEAD, typename ... TAIL> 
void ProcessArguments(const TFunc& process, const HEAD &head, const TAIL &... tail) 
{ 
    process(head); 
    ProcessArguments(process, tail...); 
} 

दूसरा विकल्प ही कर रही है, लेकिन नकल द्वारा process भेज दिया। अब, प्रतिलिपि होती है, लेकिन अभी भी अच्छी तरह से रेखांकित है।

उतना ही महत्वपूर्ण है कि process 'शरीर को भी विशेष रूप से लैम्डा के लिए रेखांकित किया जा सकता है। लैम्ब्डा ऑब्जेक्ट और उसके आकार की प्रतिलिपि बनाने की जटिलता के आधार पर, प्रतिलिपि से गुज़रने से संदर्भ में गुजरने से तेज हो सकता है या नहीं। यह तेज़ हो सकता है क्योंकि संकलक को स्थानीय प्रति की तुलना में संदर्भ के बारे में कठिन समय हो सकता है।

template <typename TFunc> 
void ProcessArguments(TFunc process) 
{} 

template <typename TFunc, typename HEAD, typename ... TAIL> 
void ProcessArguments(TFunc process, const HEAD &head, const TAIL &... tail) 
{ 
    process(head); 
    ProcessArguments(process, tail...); 
} 

तीसरा विकल्प है, ठीक है, संदर्भ के अनुसार std :: function <> को पार करने का प्रयास करें। इस तरह आप कम से कम प्रतिलिपि से बचें, लेकिन कॉल को रेखांकित नहीं किया जाएगा।

यहां कुछ perf परिणाम हैं (ideones 'C++ 11 कंपाइलर का उपयोग करके)।

Original function: 
0.483035s 

Original lambda: 
1.94531s 


Function via template copy: 
0.094748 

### Lambda via template copy: 
0.0264867s 


Function via template reference: 
0.0892594s 

### Lambda via template reference: 
0.0264201s 


Function via std::function reference: 
0.0891776s 

Lambda via std::function reference: 
0.09s 
+0

एक दिलचस्प पढ़ा! मैं बस सोच रहा हूं, आप टेम्पलेट कॉपी के माध्यम से लैम्ब्डा का सुझाव क्यों देंगे? मेरा मतलब है, संदर्भ में क्या गलत है? क्या प्रतिलिपि जोड़ने के फायदे हैं? – guyarad

+0

मैंने संदर्भ पर प्रतिलिपि का सुझाव नहीं दिया :) मैंने जो कहा वह था कि ये सभी विकल्प हैं। शायद यह बहुत स्पष्ट नहीं था। और आप यह सुनिश्चित नहीं कर सकते कि कौन सा (संदर्भ द्वारा या प्रतिलिपि द्वारा लैम्ब्डा पास करना) बेहतर है। लैम्ब्डा (इसके आकार) की सामग्री के आधार पर, इसकी प्रतिलिपि जटिलता, संदर्भ से गुजरती है या तेज़ी से नहीं हो सकती है। कंपेलरों के मूल्य से गुजर वस्तुओं के मुकाबले संदर्भों के बारे में तर्कसंगत समय हो सकता है। – biocomp

+0

शायद ऐसा इसलिए है क्योंकि मैं मूल अंग्रेजी स्पीकर नहीं हूं, लेकिन इस वाक्य ने मुझे भ्रमित कर दिया है ** आप ** वास्तव में ** वही कर सकते हैं (जोर मेरा है)। 'वास्तव में' शब्द ने मुझे यह सोचने का नेतृत्व किया कि आपका मतलब है कि यह बेहतर है ... स्पष्टीकरण के लिए धन्यवाद। – guyarad