2009-07-12 13 views
15

मैं उदाहरण के अनुसार दिखाने की कोशिश कर रहा हूं कि उपसर्ग वृद्धि पोस्टफिक्स वृद्धि से अधिक कुशल है।i ++ ++ i से कम कुशल, यह कैसे दिखाना है?

सिद्धांत में यह समझ में आता है: i ++ को अनियंत्रित मूल मान वापस करने में सक्षम होना चाहिए और इसलिए इसे स्टोर करना होगा, जबकि ++ मैं पिछले मान को संग्रहीत किए बिना वृद्धि मूल्य को वापस कर सकता हूं।

लेकिन क्या यह अभ्यास में यह दिखाने के लिए एक अच्छा उदाहरण है? फिर

gcc -Wa,-adhls -O0 myfile.cpp 

मैं ऐसा किया, पोस्टफ़िक्स वेतन वृद्धि के साथ एक उपसर्ग वेतन वृद्धि करने के लिए बदल:

int array[100]; 

int main() 
{ 
    for(int i = 0; i < sizeof(array)/sizeof(*array); i++) 
    array[i] = 1; 
} 

मैं इस तरह जीसीसी 4.4.0 का उपयोग कर इसे संकलित:

मैं निम्नलिखित कोड की कोशिश की:

for(int i = 0; i < sizeof(array)/sizeof(*array); ++i) 

परिणाम दोनों मामलों में समान असेंबली कोड है।

यह कुछ हद तक अप्रत्याशित था। ऐसा लगता है कि ऑप्टिमाइज़ेशन को बंद करके (-00) मुझे अवधारणा को दिखाने के लिए एक अंतर देखना चाहिए। मैं क्या खो रहा हूँ? क्या यह दिखाने के लिए एक बेहतर उदाहरण है? जबकि, उदा, का उपयोग

+12

संकलक यह समझने के लिए पर्याप्त स्मार्ट है कि ++ i और i ++ आपके लूप-उदाहरण में एक ही परिणाम उत्पन्न करेंगे। वास्तव में इसे एक वैरिएबल को असाइन करके और इसके साथ कुछ कंप्यूटिंग करके परिणाम का उपयोग करने का प्रयास करें, जैसे सरणी अनुक्रमणिका या कुछ। लेकिन मुझे डर है कि आप नगण्य मतभेदों को देखने जा रहे हैं। –

+1

वैसे: आकार (सरणी)/आकार (सरणी [0]) ... आकार (* सरणी) = आकार (int) – Artyom

+0

ओह ... फिक्स्ड। धन्यवाद Artyom। – cschol

उत्तर

23

सामान्य मामले में, पोस्ट वृद्धि के परिणामस्वरूप एक प्रतिलिपि होगी जिसके परिणामस्वरूप पूर्व-वृद्धि नहीं होगी। बेशक इसे बड़ी संख्या में मामलों में अनुकूलित किया जाएगा और जिन मामलों में यह प्रतिलिपि नहीं है, वे नगण्य होंगे (यानी, प्रकारों में निर्मित के लिए)।

यहां एक छोटा सा उदाहरण है जो बाद में वृद्धि की संभावित अक्षमता दिखाता है।

#include <stdio.h> 

class foo 
{ 

public: 
    int x; 

    foo() : x(0) { 
     printf("construct foo()\n"); 
    }; 

    foo(foo const& other) { 
     printf("copy foo()\n"); 
     x = other.x; 
    }; 

    foo& operator=(foo const& rhs) { 
     printf("assign foo()\n"); 
     x = rhs.x; 
     return *this; 
    }; 

    foo& operator++() { 
     printf("preincrement foo\n"); 
     ++x; 
     return *this; 
    }; 

    foo operator++(int) { 
     printf("postincrement foo\n"); 
     foo temp(*this); 
     ++x; 
     return temp; 
    }; 

}; 


int main() 
{ 
    foo bar; 

    printf("\n" "preinc example: \n"); 
    ++bar; 

    printf("\n" "postinc example: \n"); 
    bar++; 
} 

एक अनुकूलित निर्माण से परिणाम (जो वास्तव में RVO की वजह से बाद के वेतन वृद्धि के मामले में एक दूसरे की नकल आपरेशन को हटा):

construct foo() 

preinc example: 
preincrement foo 

postinc example: 
postincrement foo 
copy foo() 

सामान्य तौर पर, आप अर्थ विज्ञान की जरूरत नहीं है, तो वृद्धि के बाद, एक मौका क्यों होगा कि एक अनावश्यक प्रतिलिपि होगी?

बेशक, यह ध्यान रखना अच्छा होता है कि एक कस्टम ऑपरेटर ++() - या तो पूर्व या पोस्ट संस्करण - जो कुछ भी चाहता है उसे वापस करने के लिए स्वतंत्र है (या जो कुछ भी वह चाहता है), और मैं कल्पना करता हूं कि वहां कुछ ऐसे हैं जो सामान्य नियमों का पालन नहीं करते हैं। कभी-कभी मैं कार्यान्वयन में आया हूं जो "void" लौटाता है, जो सामान्य अर्थपूर्ण अंतर को दूर करता है।

+3

यह उदाहरण स्पष्ट रूप से i ++ की समस्या को दर्शाता है - यदि मैं एक जटिल प्रकार है जहां प्रतिलिपि महंगा है (शायद एक परिष्कृत संदर्भ इटेटरेटर गिना जाता है) तो प्रतिलिपि से बचा जाना चाहिए। –

0

कोशिश करने के लिए या दिए गए मान के साथ कुछ करना, .:

#define SOME_BIG_CONSTANT 1000000000 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int i = 1; 
    int d = 0; 

    DWORD d1 = GetTickCount(); 
    while(i < SOME_BIG_CONSTANT + 1) 
    { 
     d += i++; 
    } 
    DWORD t1 = GetTickCount() - d1; 

    printf("%d", d); 
    printf("\ni++ > %d <\n", t1); 

    i = 0; 
    d = 0; 

    d1 = GetTickCount(); 
    while(i < SOME_BIG_CONSTANT) 
    { 
     d += ++i; 

    } 
    t1 = GetTickCount() - d1; 

    printf("%d", d); 
    printf("\n++i > %d <\n", t1); 

    return 0; 
} 

का उपयोग कर/O2 या/बैल वी.एस. 2005 के साथ संकलित, मेरे डेस्कटॉप पर और लैपटॉप पर की कोशिश की।

Stably, लैपटॉप पर चारों ओर कुछ पाने डेस्कटॉप संख्या पर थोड़ा अलग हैं (लेकिन दर लगभग समान है):

i++ > 8xx < 
++i > 6xx < 

xx मतलब यह है कि संख्या अलग उदा हैं 813 बनाम 640 - अभी भी लगभग 20% गति।

और एक और बात - अगर आप की जगह "d + =" के साथ "घ =" आप देखेंगे अच्छा अनुकूलन चाल:

i++ > 935 < 
++i > 0 < 

हालांकि, यह काफी विशिष्ट है। लेकिन आखिरकार, मुझे अपने दिमाग को बदलने का कोई कारण नहीं दिखता है और लगता है कि इसमें कोई अंतर नहीं है :)

+0

जब तक 'i' और' d' पूर्णांक हैं, तो आपको यहां कोई अंतर नहीं दिखना चाहिए। –

+0

हालांकि, VS2005 में/O2 विकल्प के साथ, मुझे वास्तव में अंतर दिखाई देता है - ++ मैं तेज़ हूं। लेकिन, मैं सिर्फ उत्सुक हूं कि डीबग संस्करण में क्यों - i ++ मुझे बेहतर perfomance देता है? –

+0

मेरे परिणामों के लिए नीचे अपना दूसरा एवर देखें। –

-4

ठीक है, यह सब उपसर्ग/पोस्टफिक्स "अनुकूलन" बस कुछ बड़ी गलतफहमी है।

प्रमुख विचार यह है कि i ++ अपनी मूल प्रतिलिपि देता है और इस प्रकार मूल्य की प्रतिलिपि बनाना आवश्यक है।

यह इटरेटर के कुछ अक्षम कार्यान्वयन के लिए सही हो सकता है। हालांकि 99% मामलों में एसटीएल इटरेटर्स के साथ भी कोई फर्क नहीं पड़ता क्योंकि कंपाइलर जानता है कि इसे कैसे अनुकूलित किया जाए और वास्तविक इटरेटर केवल क्लाइंट की तरह दिखने वाले पॉइंटर्स हैं। और निश्चित रूप से पॉइंटर्स पर पूर्णांक जैसे आदिम प्रकारों के लिए कोई अंतर नहीं है।

तो ... इसके बारे में भूल जाओ।

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

मैं कहा था के रूप में, एसटीएल इटरेटर वर्गों के सबसे सिर्फ संकेत वर्गों के साथ लिपटे, सभी सदस्य कार्यों होती है इस तरह अप्रासंगिक प्रतिलिपि से बाहर अनुकूलन के लिए अनुमति देता inlined

और हाँ, यदि आपके पास इनलाइन सदस्य कार्यों के बिना आपके स्वयं के इटरेटर हैं, तो यह कार्य धीमा हो सकता है। लेकिन, आपको समझना चाहिए कि कंपाइलर क्या करता है और क्या नहीं करता है।

के रूप में एक छोटे से साबित, इस कोड को ले:

int sum1(vector<int> const &v) 
{ 
    int n; 
    for(auto x=v.begin();x!=v.end();x++) 
      n+=*x; 
    return n; 
} 

int sum2(vector<int> const &v) 
{ 
    int n; 
    for(auto x=v.begin();x!=v.end();++x) 
      n+=*x; 
    return n; 
} 

int sum3(set<int> const &v) 
{ 
    int n; 
    for(auto x=v.begin();x!=v.end();x++) 
      n+=*x; 
    return n; 
} 

int sum4(set<int> const &v) 
{ 
    int n; 
    for(auto x=v.begin();x!=v.end();++x) 
      n+=*x; 
    return n; 
} 

विधानसभा के लिए यह संकलित करें और sum1 और sum2, sum3 और sum4 तुलना ...

मैं बस बता सकते हैं ... जीसीसी दे -02 के साथ बिल्कुल वही कोड।

+4

ऐसे कई मामले हैं जहां इटरेटर * नहीं * पॉइंटर्स हैं ", और जहां कंपाइलर को इसे अनुकूलित करने का थोड़ा मौका होता है। – jalf

+0

यह महत्वपूर्ण लगता है कि हर दूसरी पाठ्य पुस्तक इसे इंगित करती है। मैं एक स्पष्ट उदाहरण ढूंढ रहा हूं प्रोग्रामिंग के लिए किसी को इस अवधारणा का प्रदर्शन करें। तो मुझे इसके बारे में भूलने के लिए मत कहो। – cschol

+1

मुझे नहीं मिलता: "मुख्य विचार है कि मैं ++ अपनी मूल प्रतिलिपि देता हूं और इस प्रकार मूल्य की प्रतिलिपि बनाना आवश्यक है। यह इटरेटर्स के कुछ अक्षम कार्यान्वयन के लिए सही हो सकता है। "यह सही है ... हमेशा। 'I ++' मूल मान वापस करने का अनुमान है। कुछ अक्षम अक्षम करना क्योंकि यह मानक का पालन करता है थोड़ा अजीब लगता है। – GManNickG

8

आप पूर्णांक के साथ कोई अंतर नहीं देखेंगे। आपको इटरेटर या कुछ ऐसा करने की ज़रूरत है जहां पोस्ट और उपसर्ग वास्तव में कुछ अलग करता है। और आपको पर सभी ऑप्टिमाइज़ेशन पर बंद करने की आवश्यकता नहीं है, बंद नहीं!

+0

जोड़ा गया है, क्या आप उस अनुकूलन टिप्पणी पर विस्तार कर सकते हैं? धन्यवाद! – cschol

+16

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

+1

मुझे लगता है कि वह सिर्फ यह देखना चाहता है कि बिना अनुकूलन के, '++ x' को' x ++ 'से तेज कोड उत्पन्न करना चाहिए। – GManNickG

5

मुझे "आपका क्या कहना है" के नियम का पालन करना पसंद है।

++i बस वृद्धि। i++ वृद्धि और में मूल्यांकन का एक विशेष, गैर-सहज परिणाम है। मैं केवल i++ का उपयोग करता हूं यदि मैं स्पष्ट रूप से उस व्यवहार को चाहता हूं, और अन्य सभी मामलों में ++i का उपयोग करें। यदि आप इस अभ्यास का पालन करते हैं, तो जब आप कोड में i++ देखते हैं, तो यह स्पष्ट है कि पोस्ट-वृद्धि व्यवहार वास्तव में इरादा था।

+8

मैं शर्त लगाता हूं कि अगर भाषा का नाम ++ सी रखा गया है, तो लोग इसे डिफ़ॉल्ट रूप से अधिक बार करेंगे। – Reunanen

+5

शायद इसे सी ++ कहा जाता है क्योंकि यह अभी भी एक निश्चित तरीके से उपयोग किए जाने पर सी की तरह कार्य करता है। –

+2

"जब आप कोड में i ++ देखते हैं, तो यह स्पष्ट है कि पोस्ट-वृद्धि व्यवहार वास्तव में इरादा था।" आपको विशेष कुछ लोगों में से एक होना चाहिए जो अंतर को याद करते हैं ;-) –

0

यह कोड और इसकी टिप्पणियों को दोनों के बीच अंतर प्रदर्शित करना चाहिए।

class a { 
    int index; 
    some_ridiculously_big_type big; 

    //etc... 

}; 

// prefix ++a 
void operator++ (a& _a) { 
    ++_a.index 
} 

// postfix a++ 
void operator++ (a& _a, int b) { 
    _a.index++; 
} 

// now the program 
int main (void) { 
    a my_a; 

    // prefix: 
    // 1. updates my_a.index 
    // 2. copies my_a.index to b 
    int b = (++my_a).index; 

    // postfix 
    // 1. creates a copy of my_a, including the *big* member. 
    // 2. updates my_a.index 
    // 3. copies index out of the **copy** of my_a that was created in step 1 
    int c = (my_a++).index; 
} 

आप देख सकते हैं पोस्टफ़िक्स एक अतिरिक्त कदम (चरण 1) जो वस्तु की एक प्रतिलिपि बनाना शामिल है। इसमें मेमोरी खपत और रनटाइम दोनों के लिए दोनों प्रभाव हैं। वह यही कारण है कि उपसर्ग अधिक कुशल है कि गैर-मूल प्रकारों के लिए पोस्टफिक्स।

some_ridiculously_big_type पर निर्भर करता है और जो भी आप incrememt के परिणाम के साथ करते हैं, आप ऑप्टिमाइज़ेशन के साथ या बिना अंतर को देख पाएंगे।

4

कई अंक:

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

यदि आप अंतर दिखाना चाहते हैं, तो सबसे आसान विकल्प केवल ऑपरेटरों को अपनाने के लिए है, और इंगित करें कि किसी को एक अतिरिक्त प्रति की आवश्यकता है, दूसरा नहीं।

0

Mihail के जवाब में, यह एक कुछ और अधिक पोर्टेबल संस्करण अपने कोड है:

#include <cstdio> 
#include <ctime> 
using namespace std; 

#define SOME_BIG_CONSTANT 100000000 
#define OUTER 40 
int main(int argc, char * argv[]) { 

    int d = 0; 
    time_t now = time(0); 
    if (argc == 1) { 
     for (int n = 0; n < OUTER; n++) { 
      int i = 0; 
      while(i < SOME_BIG_CONSTANT) { 
       d += i++; 
      } 
     } 
    } 
    else { 
     for (int n = 0; n < OUTER; n++) { 
      int i = 0; 
      while(i < SOME_BIG_CONSTANT) { 
       d += ++i; 
      } 
     } 
    } 
    int t = time(0) - now; 
    printf("%d\n", t); 
    return d % 2; 
} 

बाहरी छोरों वहाँ मुझे समय बेला के लिए मेरी मंच पर उपयुक्त कुछ पाने के लिए अनुमति देने के लिए कर रहे हैं।

मैं के साथ कुलपति उपयोग नहीं करते ++ किसी भी अधिक है, तो मैं इसे संकलित (Windows पर):

a.exe 

और

a.exe 1 
:

g++ -O3 t.cpp 

मैं तो बारी से यह भागा

मेरे समय के परिणाम दोनों मामलों के लिए लगभग समान थे। कभी-कभी एक संस्करण 20% तक और कभी-कभी दूसरे तक तेज होगा। यह मुझे लगता है कि मेरे सिस्टम पर चल रही अन्य प्रक्रियाओं के कारण है।

+0

तो, विभिन्न उपकरणों के साथ संकलित और विभिन्न प्लेटफार्मों पर चलते समय ऐसा लगता है कि यह काफी अलग व्यवहार है। –

+0

कृपया उस धारणा से पहले अपने कंपाइलर के साथ अपना कोड आज़माएं। –

+0

10-11 बनाम 13. अधिक जटिल प्रोग्राम है, ++ में कम अंतर दिखाई देगा। –

0

शायद आप x86 असेंबली निर्देशों के साथ दोनों संस्करणों को लिखकर सैद्धांतिक अंतर दिखा सकते हैं? जैसा कि कई लोगों ने पहले बताया है, कंपाइलर हमेशा अपने फैसले बनाएगा कि इस कार्यक्रम को कैसे संकलित/इकट्ठा करना है।

यदि उदाहरण x86 निर्देश सेट से परिचित छात्रों के लिए नहीं है, तो आप MIPS32 निर्देश सेट का उपयोग करने पर विचार कर सकते हैं - कुछ अजीब कारणों से कई लोगों को x86 असेंबली की तुलना में समझना आसान लगता है।

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