2013-09-05 6 views
9

रिटर्न वैल्यू ऑप्टिमाइज़ेशन की मेरी समझ यह है कि संकलक गुप्त रूप से ऑब्जेक्ट का पता पास करता है जिसमें रिटर्न वैल्यू संग्रहीत किया जाएगा, और स्थानीय चर के बजाय उस ऑब्जेक्ट में परिवर्तन करता है।किसी फ़ंक्शन के कॉलर को पता है कि रिटर्न वैल्यू ऑप्टिमाइज़ेशन का उपयोग किया गया था या नहीं?

उदाहरण के लिए, कोड

std::string s = f(); 

std::string f() 
{ 
    std::string x = "hi"; 
    return x; 
} 

std::string s; 
f(s); 

void f(std::string& x) 
{ 
    x = "hi"; 
} 

के समान हो जाता है जब RVO प्रयोग किया जाता है। इसका मतलब है कि फ़ंक्शन का इंटरफ़ेस बदल गया है, क्योंकि एक अतिरिक्त छुपा पैरामीटर है।

अब निम्नलिखित मामले मैं विकिपीडिया

से चुरा लिया पर विचार
std::string f(bool cond) 
{ 
    std::string first("first"); 
    std::string second("second"); 
    // the function may return one of two named objects 
    // depending on its argument. RVO might not be applied 
    return cond ? first : second; 
} 

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

+3

प्रत्येक कंपाइलर इसे किसी भी तरह से करने के लिए चुन सकता है, और यदि आपके द्वारा वर्णित मामले में उनके लिए कोई समस्या है, तो संभवतः वे हमेशा आरवीओ का उपयोग करना चुनेंगे। जब आप घुसपैठ कर रहे हैं कि कैसे विभिन्न कंपेलर हुड के नीचे करते हैं, तो मेरा सुझाव है कि आप जेनरेट किए गए असेंबलर कोड को पढ़ लें। क्लैंग/जीसीसी जेनरेट असेंबली तक आसान पहुंच के लिए जीसीसी एक्सप्लोरर का उपयोग करें। – PlasmaHH

+0

@PlasmaHH लेकिन आरवीओ हमेशा संभव नहीं है। –

+1

इसका मतलब यह नहीं है कि जब कॉलिंग सम्मेलन में होता है या इसका उपयोग नहीं किया जाता है। कुछ असेंबलर को देखें कि वे इसे कैसे करते हैं। – PlasmaHH

उत्तर

7

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

मैं यह जोड़ सकता हूं कि आपका दूसरा उदाहरण के बहुत करीब है। कॉल साइट पर, आप लगभग हमेशा की तरह कुछ मिलती है:

<raw memory for string> s; 
f(&s); 

और कहा जाता समारोह या तो एक स्थानीय चर या पते पर सीधे अस्थायी यह पारित किया गया था का निर्माण करेगी, या कॉपी इस पर कुछ othe मूल्य का निर्माण पता। तो अपने पिछले उदाहरण में, वापसी कथन होगा कि कम या ज्यादा की बराबर:

if (cont) { 
    std::string::string(s, first); 
} else { 
    std::string::string(s, second); 
} 

पहले मामले में (। अंतर्निहित this सूचक प्रति निर्माता के लिए पारित दिखा रहा है), RVO लागू होता है अगर , विशेष कोड x के निर्माता में होगा:

std::string::string(s, "hi"); 

और फिर समारोहमें अन्य सभी स्थानों *s साथ x की जगह(और वापसी में कुछ भी नहीं कर रहा है)।

+0

तो यह एक छुपा पैरामीटर जोड़ता है और फ़ंक्शन शून्य बनाता है? (स्पष्ट रूप से हुड के नीचे) – xanatos

+2

@xanatos: असेंबलर स्तर पर कोई "शून्य" कार्य नहीं है, ऐसे स्थानों पर सम्मेलन हैं जिन पर स्थानों को वापस मूल्य दिया जाता है और जहां कॉलर उन्हें उम्मीद करता है, "शून्य" केवल वह मामला है जहां संख्या वापसी मूल्यों का 0 0. – PlasmaHH

+1

@PlasmaHH और असेंबलर स्तर पर कोई पैरामीटर नहीं हैं ... केवल चीजें जिन्हें किसी ने ढेर पर धक्का दिया है या एक रजिस्टर में डाल दिया है। हम इस खेल को पूरे दिन खेल सकते हैं। आइए मान लें कि यह स्टैक पर रिटर्न वैल्यू नहीं डालता है या इसे रजिस्टर में नहीं डालता है, इसलिए यह एक शून्य कार्य के समान है। – xanatos

2

एनआरवीओ, आरवीओ और कॉपी एलिशन के साथ खेलें!

#include <iostream> 
struct Verbose { 
    Verbose(Verbose const&){ std::cout << "copy ctor\n"; } 
    Verbose(Verbose &&){ std::cout << "move ctor\n"; } 
    Verbose& operator=(Verbose const&){ std::cout << "copy asgn\n"; } 
    Verbose& operator=(Verbose &&){ std::cout << "move asgn\n"; } 
}; 

कि बहुत वर्बोज़ है:

यहाँ एक प्रकार है।

Verbose simple() { return {}; } 

कि बहुत सरल है, और इसकी वापसी मान के प्रत्यक्ष निर्माण का उपयोग करता है:

यहाँ एक समारोह है। यदि Verbose में कॉपी या मूवी कन्स्ट्रक्टर की कमी है, तो उपरोक्त फ़ंक्शन काम करेगा!

Verbose simple_RVO() { return Verbose(); } 
यहाँ

अनाम Verbose() अस्थायी वस्तु रिटर्न मान पर स्वयं की कॉपी करने के लिए कहा जा रहा है:

यहाँ एक समारोह का उपयोग करता है RVO है। आरवीओ का मतलब है कि संकलक उस प्रतिलिपि को छोड़ सकता है, और वापसी मूल्य में सीधे Verbose() का निर्माण कर सकता है, अगर केवल एक प्रतिलिपि या कन्स्ट्रक्टर है तो केवल तभी। प्रतिलिपि या चालक कन्स्ट्रक्टर नहीं कहा जाता है, बल्कि इसके बजाय।

यहाँ एक समारोह NRVO का उपयोग करता है:

Verbose simple_NRVO() { 
    Verbose retval; 
    return retval; 
} 

NRVO होने के लिए, हर पथ ठीक उसी वस्तु लौटना चाहिए, और आप इसके बारे में डरपोक नहीं किया जा सकता है (यदि आप करने के लिए दिया गया मान डाली एक संदर्भ, फिर उस संदर्भ को वापस करें, जो एनआरवीओ को अवरुद्ध करेगा)। इस मामले में, कंपाइलर क्या करता है नामित ऑब्जेक्ट retval सीधे वापसी मूल्य स्थान में बनाता है। आरवीओ के समान, एक प्रतिलिपि या चालक निर्माता मौजूद होना चाहिए, लेकिन इसे नहीं कहा जाता है। ,,

Verbose simple_no_NRVO(bool b) { 
    Verbose retval1; 
    Verbose retval2; 
    if (b) 
    return retval1; 
    else 
    return retval2; 
} 

इसे वापस कर सकता है के रूप में दो संभावित नामित वस्तुओं रहे हैं यह वापसी मान स्थान में उन दोनों का निर्माण नहीं कर सकते हैं तो यह कार्य करना होगा:

यहाँ एक समारोह है कि NRVO का उपयोग करने में विफल रहता है एक वास्तविक प्रति। सी ++ 11 में, प्रतिलिपि की गई वस्तु प्रतिलिपि के बजाय move डी होगी, क्योंकि यह एक स्थानीय चर को एक साधारण रिटर्न स्टेटमेंट में फ़ंक्शन से वापस किया जा रहा है। तो कम से कम वह है।

Verbose v = simple(); // or simple_RVO, or simple_NRVO, or... 

जब आप एक समारोह फोन है, आप इसे अपने तर्कों के साथ प्रदान करते हैं, और आप इसे सूचित जहां यह अपनी वापसी मान रखना चाहिए:

अंत में, दूसरे छोर पर प्रतिलिपि इलिजन है। कॉलर वापसी मूल्य को साफ करने और इसके लिए स्मृति (ढेर पर) आवंटित करने के लिए ज़िम्मेदार है।

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

कई कॉलिंग सम्मेलनों के तहत, स्थान जहां वापसी मूल्य संग्रहीत किया जा सकता है, स्थानीय चर के रूप में उपयोग किया जा सकता है।

सामान्य में, अगर आप फार्म के एक चर राशि:

Verbose v = Verbose(); 

गर्भित प्रतिलिपि elided जा सकता है - Verbose() बजाय एक अस्थायी किया जा रहा बनाया तो v में कॉपी किया, v में सीधे निर्माण किया है।इसी तरह, simple (या simple_NRVO, या जो कुछ भी) का वापसी मूल्य तब किया जा सकता है जब संकलक का रन टाइम मॉडल इसका समर्थन करता है (और यह आमतौर पर करता है)।

असल में, कॉलिंग साइट simple_* को किसी विशेष स्थान पर वापसी मूल्य डालने के लिए बता सकती है, और बस उस स्थान को स्थानीय चर v के रूप में मान सकती है।

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

इसी तरह, कॉलिंग साइट पर eliding फ़ंक्शन के बाहर किया गया है, और यदि कॉलिंग सम्मेलन इसका समर्थन करता है तो आपको फ़ंक्शन के शरीर से किसी भी समर्थन की आवश्यकता नहीं है।

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

+0

यदि हम अलग-अलग मामलों में चर बनाने के लिए "simple_no_NRVO" का उदाहरण बदलते हैं, तो अन्यथा लागू होंगे? शब्दाडंबरपूर्ण simple_no_NRVO (bool ख) { \t \t अगर (ख) \t \t { \t \t \t शब्दाडंबरपूर्ण retval1; \t \t \t वापसी retval1; \t \t} \t बाकी \t \t { \t \t \t शब्दाडंबरपूर्ण retval2 \t; \t \t \t वापसी retval2; \t \t} } –

संबंधित मुद्दे

 संबंधित मुद्दे