क्या आधुनिक C ++ आपको मुफ्त में प्रदर्शन दे सकता है?


205

कभी-कभी यह दावा किया जाता है कि C ++ 11/14 केवल C ++ 98 कोड को संकलित करने पर भी आपको एक प्रदर्शन को बढ़ावा मिल सकता है। औचित्य आम तौर पर चाल शब्दार्थों की तर्ज पर होता है, क्योंकि कुछ मामलों में रूवल निर्माणकर्ता स्वचालित रूप से उत्पन्न होते हैं या अब एसटीएल का हिस्सा होते हैं। अब मैं सोच रहा हूँ कि क्या ये मामले वास्तव में पहले से ही आरवीओ या इसी तरह के संकलक अनुकूलन द्वारा संभाले हुए थे।

मेरा प्रश्न यह है कि क्या आप मुझे C ++ 98 कोड के एक टुकड़े का वास्तविक उदाहरण दे सकते हैं, जो संशोधन के बिना, नई भाषा सुविधाओं का समर्थन करने वाले कंपाइलर का उपयोग करके तेजी से चलता है। मुझे लगता है कि एक मानक अनुरूप संकलक की जरूरत नहीं है कि वह नकल चीरा कर सके और सिर्फ इसी कारण से शब्दार्थ गति ला सकता है, लेकिन मैं एक कम रोग संबंधी मामला देखना चाहूंगा, यदि आप करेंगे।

संपादित करें: बस स्पष्ट होने के लिए, मैं यह नहीं पूछ रहा हूं कि क्या पुराने संकलक की तुलना में नए संकलक तेज हैं, बल्कि अगर कोड है जिससे -std = c ++ 14 मेरे संकलक झंडे में तेजी से भाग जाएगा (प्रतियों से बचें, लेकिन यदि आप चाल शब्दार्थ के अलावा कुछ और भी आ सकता है, मुझे भी दिलचस्पी होगी)


3
याद रखें कि कॉपी कंस्ट्रक्टर का उपयोग करके एक नई वस्तु का निर्माण करते समय कॉपी एलीशन और रिटर्न वैल्यू ऑप्टिमाइज़ेशन किया जाता है। हालांकि, एक कॉपी असाइनमेंट ऑपरेटर में, कोई कॉपी एलिसेंस नहीं है (यह कैसे हो सकता है, क्योंकि कंपाइलर को यह नहीं पता है कि पहले से बनाई गई वस्तु के साथ क्या करना है जो अस्थायी नहीं है)। इसलिए, उस स्थिति में, C ++ 11/14 आपको एक चाल असाइनमेंट ऑपरेटर का उपयोग करने की संभावना देकर, बड़ी जीत देता है। हालांकि आपके प्रश्न के बारे में, मुझे नहीं लगता कि C ++ 11/14 संकलक द्वारा संकलित किए जाने पर C ++ 98 कोड तेज होना चाहिए, शायद यह तेज है क्योंकि संकलक नया है।
विस्मयादिबोधक

27
साथ ही मानक पुस्तकालय का उपयोग करने वाला कोड संभावित रूप से तेज होता है, भले ही आप इसे C ++ 98 के साथ पूरी तरह से संगत बनाते हों, क्योंकि C ++ 11/14 में अंतर्निहित पुस्तकालय आंतरिक रूप से संभव होने पर शब्दार्थ का उपयोग करता है। तो C ++ 98 और C ++ 11/14 में समान दिखने वाला कोड बाद के मामले में (संभवतः) तेजी से होगा, जब भी आप मानक पुस्तकालय वस्तुओं जैसे कि वैक्टर, सूची इत्यादि का उपयोग करते हैं और शब्दार्थ को स्थानांतरित करते हैं तो फर्क पड़ता है।
vsoftco

1
@vsoftco, यह उस तरह की स्थिति है जिसे मैं समझ रहा था, लेकिन एक उदाहरण के साथ नहीं आ सका: मुझे जो याद है अगर मुझे कॉपी कंस्ट्रक्टर को परिभाषित करना है, तो मूव कंस्ट्रक्टर स्वचालित रूप से उत्पन्न नहीं होगा, जो हमें छोड़ देता है बहुत सरल कक्षाएं जहां आरवीओ, मुझे लगता है, हमेशा काम करता है। एसटीएल कंटेनरों के साथ एक अपवाद कुछ हो सकता है, जहां लाइब्रेरी कंस्ट्रक्टर द्वारा प्रतिद्वंद्वियों का निर्माण किया जाता है (इसका अर्थ है कि मुझे चाल का उपयोग करने के लिए कोड में कुछ भी बदलना नहीं होगा)।
अलार्गे

कॉपी कंस्ट्रक्टर न होने के लिए कक्षाओं को सरल होने की आवश्यकता नहीं है। C ++ मूल्य शब्दार्थ पर पनपे, और कॉपी कंस्ट्रक्टर, असाइनमेंट ऑपरेटर, डिस्ट्रक्टर आदि को अपवाद होना चाहिए।
sp2danny

1
@ इस लिंक के लिए धन्यवाद, यह दिलचस्प था। हालाँकि, जल्दी से इसके माध्यम से देखा गया, इसमें गति लाभ ज्यादातर std::moveकंस्ट्रक्टरों को जोड़ने और स्थानांतरित करने से आता है (जिन्हें मौजूदा कोड में संशोधनों की आवश्यकता होगी)। मेरे सवाल से संबंधित एकमात्र बात यह थी कि वाक्य "आपको तत्काल गति के लाभ बस पुनः प्राप्त करने से मिलते हैं", जो किसी भी उदाहरण से समर्थित नहीं है (यह उसी स्लाइड पर एसटीएल का उल्लेख करता है, जैसा कि मैंने अपने प्रश्न में किया था, लेकिन कुछ भी विशिष्ट नहीं है )। मैं कुछ उदाहरणों के लिए पूछ रहा था। अगर मैं स्लाइड्स को गलत पढ़ रहा हूं, तो मुझे बताएं।
अलारज

जवाबों:


221

मैं 5 सामान्य श्रेणियों से अवगत हूं, जहां C ++ 03 कंपाइलर को C ++ 11 के रूप में फिर से जमा करना अनियोजित प्रदर्शन बढ़ सकता है जो व्यावहारिक रूप से कार्यान्वयन की गुणवत्ता से असंबंधित हैं। ये सभी कदम शब्दार्थ के रूपांतर हैं।

std::vector पुनः निर्दिष्ट करें

struct bar{
  std::vector<int> data;
};
std::vector<bar> foo(1);
foo.back().data.push_back(3);
foo.reserve(10); // two allocations and a delete occur in C++03

हर बार जब fooबफर को C ++ 03 में दोबारा लाया जाता है तो वह हर vectorमें कॉपी हो जाता है bar

C ++ 11 में यह bar::dataएस को स्थानांतरित करता है , जो मूल रूप से स्वतंत्र है।

इस मामले में, यह stdकंटेनर के अंदर अनुकूलन पर निर्भर करता है vector। नीचे दिए गए प्रत्येक मामले में, stdकंटेनरों का उपयोग सिर्फ इसलिए होता है क्योंकि वे moveC ++ 11 C "11" स्वचालित रूप से आपके कंपाइलर को अपग्रेड करने के लिए कुशल शब्दार्थ हैं। ऑब्जेक्ट जो इसे ब्लॉक नहीं करते हैं जिनमें एक stdकंटेनर होता है, जो स्वचालित रूप से बेहतर moveनिर्माण करने वालों को विरासत में मिलता है ।

NRVO की विफलता

जब NRVO (नाम वापसी मूल्य अनुकूलन) विफल रहता है, C ++ 03 में यह कॉपी पर वापस आ जाता है, C ++ 11 पर यह वापस आ जाता है। NRVO की विफलताएं आसान हैं:

std::vector<int> foo(int count){
  std::vector<int> v; // oops
  if (count<=0) return std::vector<int>();
  v.reserve(count);
  for(int i=0;i<count;++i)
    v.push_back(i);
  return v;
}

या और भी:

std::vector<int> foo(bool which) {
  std::vector<int> a, b;
  // do work, filling a and b, using the other for calculations
  if (which)
    return a;
  else
    return b;
}

हमारे पास तीन मान हैं - रिटर्न वैल्यू, और फ़ंक्शन के भीतर दो अलग-अलग मान। एलीशन फ़ंक्शन के भीतर मूल्यों को रिटर्न वैल्यू के साथ 'मर्ज' करने की अनुमति देता है, लेकिन एक दूसरे के साथ नहीं। वे दोनों एक दूसरे के साथ विलय किए बिना रिटर्न वैल्यू में विलय नहीं हो सकते।

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

एक फ़ंक्शन तर्क लौटा रहा है

यहां भी चीरा असंभव है:

std::set<int> func(std::set<int> in){
  return in;
}

C ++ 11 में यह सस्ता है: C ++ 03 में कॉपी से बचने का कोई तरीका नहीं है। फ़ंक्शंस के लिए तर्क को रिटर्न वैल्यू के साथ जोड़ा नहीं जा सकता है, क्योंकि जीवनकाल और पैरामीटर और रिटर्न वैल्यू का स्थान कॉलिंग कोड द्वारा प्रबंधित किया जाता है।

हालाँकि, C ++ 11 एक से दूसरे में जा सकता है। (कम खिलौना उदाहरण में, कुछ किया जा सकता है set)।

push_back या insert

अंत में कंटेनरों में चीरा नहीं होता है: लेकिन C ++ 11 ओवरलोड्स आवेषण ऑपरेटरों को स्थानांतरित करते हैं, जो प्रतियों को बचाता है।

struct whatever {
  std::string data;
  int count;
  whatever( std::string d, int c ):data(d), count(c) {}
};
std::vector<whatever> v;
v.push_back( whatever("some long string goes here", 3) );

C ++ 03 में एक अस्थायी whateverबनाया जाता है, फिर इसे वेक्टर में कॉपी किया जाता है v। 2 std::stringबफ़र्स आवंटित किए जाते हैं, प्रत्येक समान डेटा के साथ, और एक को छोड़ दिया जाता है।

C ++ 11 में एक अस्थायी whateverबनाया जाता है। whatever&& push_backअधिभार तो moveहै कि वेक्टर में अस्थायी है v। एक std::stringबफर आवंटित किया गया है, और वेक्टर में स्थानांतरित कर दिया गया है। एक खाली std::stringछोड़ दिया जाता है।

असाइनमेंट

नीचे @ Jarod42 के जवाब से चोरी।

एलिसन असाइनमेंट के साथ नहीं हो सकता है, लेकिन चाल-से कर सकते हैं।

std::set<int> some_function();

std::set<int> some_value;

// code

some_value = some_function();

यहाँ से some_functionएक उम्मीदवार को वापस लाने के लिए है, लेकिन क्योंकि यह सीधे एक वस्तु का निर्माण करने के लिए उपयोग नहीं किया जाता है, यह elided नहीं किया जा सकता है। C ++ 03 में, अस्थायी रूप से कॉपी की जा रही सामग्री में उपरोक्त परिणाम some_value। C ++ 11 में, इसे ले जाया जाता है some_value, जो मूल रूप से मुफ़्त है।


उपरोक्त पूर्ण प्रभाव के लिए, आपको एक कंपाइलर की आवश्यकता होती है जो आपके लिए मूवमेंट कंस्ट्रक्टर और असाइनमेंट को संश्लेषित करता है।

MSVC 2013 लागू करने वाले कंस्ट्रक्टरों को stdकंटेनरों में ले जाते हैं , लेकिन आपके प्रकारों पर मूव कंस्ट्रक्टर्स को संश्लेषित नहीं करते हैं।

इसलिए std::vectors और इसी तरह के प्रकारों को MSVC2013 में इस तरह के सुधार नहीं मिलते हैं, लेकिन MSVC2015 में उन्हें मिलना शुरू हो जाएगा।

क्लैंग और जीसीसी लंबे समय से निहित चाल निर्माणकर्ताओं को लागू करते हैं। यदि आप पास हो -Qoption,cpp,--gen_move_operationsजाते हैं, तो Intel का 2013 कंपाइलर चाल निर्माणकर्ताओं की अंतर्निहित पीढ़ी का समर्थन करेगा (वे MSVC2013 के साथ संगत होने के प्रयास में डिफ़ॉल्ट रूप से ऐसा नहीं करते हैं)।


1
@ कलेजा हाँ। लेकिन एक मूव कंस्ट्रक्टर के लिए एक कॉपी कंस्ट्रक्टर की तुलना में कई गुना अधिक कुशल होने के लिए, आमतौर पर उन्हें कॉपी करने के बजाय संसाधनों को स्थानांतरित करना पड़ता है। अपने स्वयं के चाल निर्माणकर्ताओं को लिखे बिना (और सिर्फ C ++ 03 प्रोग्राम को फिर से शुरू करना), stdपुस्तकालय कंटेनरों को move"मुफ्त में" निर्माणकर्ताओं के साथ अपडेट किया जा रहा है , और (यदि आपने इसे ब्लॉक नहीं किया है) तो निर्माण करने वाले ऑब्जेक्ट्स का उपयोग करते हैं ( और कहा कि वस्तुएं) कई स्थितियों में मुफ्त चाल निर्माण शुरू कर देंगी। उन स्थितियों में से कई C ++ 03 में elision द्वारा कवर किए गए हैं: सभी नहीं।
यक - एडम नेवरामॉन्ट

5
यह एक बुरा ऑप्टिमाइज़र कार्यान्वयन है, फिर, क्योंकि अलग-अलग नामित वस्तुओं को लौटाया जा रहा है, जीवन भर ओवरलैपिंग नहीं है, आरवीओ सैद्धांतिक रूप से अभी भी संभव है।
बेन वोइगट

2
@algege ऐसी जगहें हैं जहां चीरा विफल हो जाता है, जैसे जब अतिव्यापी जीवनकाल वाली दो वस्तुओं को एक दूसरे में नहीं बल्कि एक दूसरे में डाला जा सकता है। फिर C ++ 11 में मूव की आवश्यकता होती है, और C ++ 03 में कॉपी करना (जैसे-यदि नजरअंदाज करना हो)। व्यवहार में अक्सर चीरा नाजुक होता है। stdऊपर दिए गए कंटेनरों का उपयोग ज्यादातर इसलिए होता है क्योंकि वे C ++ 03 को पुन: जमा करते समय C ++ 11 में 'मुफ्त में' प्राप्त करने के लिए अतिउत्साह में ले जाने के लिए सस्ते होते हैं। vector::resizeएक अपवाद है: इसे इस्तेमाल करता है moveC ++ 11।
यक्क - एडम नेवरामॉन्ट

27
मैं केवल 1 सामान्य श्रेणी देख रहा हूं जो कि शब्दार्थ है, और उस के 5 विशेष मामले हैं।
जोहान्स शाउब -

3
@ मुझे समझ में आता है, आप "कई किलोबाइट आवंटन के कई प्रकार के आवंटन नहीं करने के लिए कार्यक्रम का कारण नहीं मानते हैं, और इसके बजाय पर्याप्त होने के लिए संकेत चारों ओर ले जाते हैं"। आप समयबद्ध परिणाम चाहते हैं। माइक्रोबेन्चार्म्स प्रदर्शन के सुधार का कोई और सबूत नहीं हैं सबूत की तुलना में आप मूलभूत रूप से कम कर रहे हैं। वास्तविक दुनिया के कार्यों की रूपरेखा के साथ कई तरह के उद्योगों में 100 वास्तविक दुनिया के अनुप्रयोगों में से कुछ को वास्तव में प्रमाण नहीं माना जाता है। मैंने "मुक्त प्रदर्शन" के बारे में अस्पष्ट दावे लिए और उन्हें C ++ 03 और C ++ 11 के तहत कार्यक्रम व्यवहार में अंतर के बारे में विशिष्ट तथ्य दिए।
यक - एडम नेवेरुमोंट

46

अगर आपके पास कुछ है:

std::vector<int> foo(); // function declaration.
std::vector<int> v;

// some code

v = foo();

आपको C ++ 03 में एक कॉपी मिली, जबकि आपको C ++ 11 में मूव असाइनमेंट मिला। तो आप उस मामले में मुक्त अनुकूलन है।


4
@ यक: असाइनमेंट में कॉपी एलिसेंस कैसे होता है?
Jarod42

2
@ Jarod42 मैं यह भी मानता हूं कि एक कार्य में कॉपी एलिसेंस संभव नहीं है, क्योंकि बाएं हाथ की तरफ पहले से ही निर्माण किया गया है और एक कंपाइलर के लिए यह जानने का कोई उचित तरीका नहीं है कि संसाधनों को सही से चोरी करने के बाद "पुराने" डेटा के साथ क्या करना है। हाथ की तरफ। लेकिन शायद मैं गलत हूं, मैं एक बार और हमेशा के लिए जवाब देना पसंद करूंगा। जब आप निर्माण को कॉपी करते हैं तब कॉपी एलिसन समझ में आता है, क्योंकि ऑब्जेक्ट "ताजा" है और पुराने डेटा के साथ क्या करना है, यह तय करने की कोई समस्या नहीं है। जहाँ तक मुझे पता है, इसका एकमात्र अपवाद यह है: "असाइनमेंट केवल के रूप में-अगर नियम के आधार पर खत्म किया जा सकता है"
vsoftco

4
अच्छा C ++ 03 कोड पहले से ही इस मामले में एक कदम था, के माध्यम सेfoo().swap(v);
बेन Voigt

@BenVoigt यकीन है, लेकिन सभी कोड अनुकूलित नहीं हैं, और सभी स्पॉट नहीं हैं जहां ऐसा होता है, जहां तक ​​पहुंचना आसान है।
यक्क - एडम नेवरामोंट

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