जीसीसी 5.4.0 के साथ एक महंगी छलांग


171

मेरे पास एक फंक्शन था जो इस तरह दिखता था (केवल महत्वपूर्ण भाग दिखाते हुए):

double CompareShifted(const std::vector<uint16_t>& l, const std::vector<uint16_t> &curr, int shift, int shiftY)  {
...
  for(std::size_t i=std::max(0,-shift);i<max;i++) {
     if ((curr[i] < 479) && (l[i + shift] < 479)) {
       nontopOverlap++;
     }
     ...
  }
...
}

इस तरह लिखा, फंक्शन ने मेरी मशीन पर ~ 34ms ले लिया। बूल गुणा करने के लिए स्थिति बदलने के बाद (इस तरह कोड बनाना):

double CompareShifted(const std::vector<uint16_t>& l, const std::vector<uint16_t> &curr, int shift, int shiftY)  {
...
  for(std::size_t i=std::max(0,-shift);i<max;i++) {
     if ((curr[i] < 479) * (l[i + shift] < 479)) {
       nontopOverlap++;
     }
     ...
  }
...
}

निष्पादन का समय घटकर ~ 19ms हो गया।

संकलक का उपयोग किया गया था GCC 5.4.0 -O3 के साथ और Godbolt.org का उपयोग करके उत्पन्न एएसएम कोड की जांच करने के बाद मुझे पता चला कि पहला उदाहरण एक कूद पैदा करता है, जबकि दूसरा नहीं करता है। मैंने जीसीसी 6.2.0 की कोशिश करने का फैसला किया, जो पहले उदाहरण का उपयोग करते समय एक कूदने का निर्देश भी देता है, लेकिन जीसीसी 7 अब एक भी उत्पन्न नहीं करता है।

कोड को गति देने के इस तरीके का पता लगाने के बजाय भीषण था और इसमें कुछ समय लगा। संकलक इस तरह से व्यवहार क्यों करता है? क्या यह इरादा है और यह ऐसा कुछ है जिसे प्रोग्रामर को देखना चाहिए? क्या इसी तरह की और भी चीजें हैं?

EDIT: Godbolt का लिंक https://godbolt.org/g/5lKPF3


17
संकलक इस तरह से व्यवहार क्यों करता है? कंपाइलर अपनी इच्छानुसार कर सकता है, जब तक कि उत्पन्न कोड सही है। कुछ संकलक दूसरों की तुलना में अनुकूलन पर बेहतर हैं।
जाबेरवॉकी

26
मेरा अनुमान है कि इसके कारण शॉर्ट-सर्किट मूल्यांकन है &&
जेन्स

9
ध्यान दें कि यही कारण है कि हमारे पास भी है &
रुबनेव

7
@ याकूब छांटने से संभवतः निष्पादन की गति में वृद्धि होगी, यह प्रश्न देखें ।
रुबनेव

8
@rubenvb "का मूल्यांकन नहीं किया जाना चाहिए" वास्तव में अभिव्यक्ति के लिए कुछ भी मतलब नहीं है जिसका कोई दुष्प्रभाव नहीं है। मुझे संदेह है कि वेक्टर सीमा-जाँच करता है और जीसीसी यह साबित नहीं कर सकता कि यह सीमा से बाहर नहीं होगा। संपादित करें: वास्तव में, मुझे नहीं लगता कि आप कुछ भी करने से बच रहे हैं i + पारी से बाहर होने से।
रैंडम 832

जवाबों:


263

लॉजिकल एंड ऑपरेटर ( &&) शॉर्ट-सर्किट मूल्यांकन का उपयोग करता है, जिसका अर्थ है कि दूसरा परीक्षण केवल तभी किया जाता है जब पहली तुलना सच का मूल्यांकन करती है। यह अक्सर वही शब्दार्थ होता है जिसकी आपको आवश्यकता होती है। उदाहरण के लिए, निम्नलिखित कोड पर विचार करें:

if ((p != nullptr) && (p->first > 0))

आपको यह सुनिश्चित करना चाहिए कि आपके द्वारा इसे स्थगित करने से पहले सूचक गैर-शून्य है। यदि यह एक शॉर्ट-सर्किट मूल्यांकन नहीं था , तो आपके पास अपरिभाषित व्यवहार होगा क्योंकि आप एक अशक्त सूचक को निष्क्रिय कर रहे होंगे।

यह भी संभव है कि शॉर्ट सर्किट मूल्यांकन उन मामलों में एक प्रदर्शन लाभ देता है जहां स्थितियों का मूल्यांकन एक महंगी प्रक्रिया है। उदाहरण के लिए:

if ((DoLengthyCheck1(p) && (DoLengthyCheck2(p))

यदि DoLengthyCheck1विफल रहता है, तो कॉल करने का कोई मतलब नहीं है DoLengthyCheck2

हालांकि, परिणामस्वरूप बाइनरी में, शॉर्ट-सर्किट ऑपरेशन अक्सर दो शाखाओं में परिणत होता है, क्योंकि यह कंपाइलर के लिए इन शब्दार्थों को संरक्षित करने का सबसे आसान तरीका है। (यही कारण है कि, सिक्के के दूसरी तरफ, शॉर्ट-सर्किट मूल्यांकन कभी-कभी अनुकूलन क्षमता को बाधित कर सकता है ।) आप इसे ifGCC 5.4 द्वारा अपने बयान के लिए बनाए गए ऑब्जेक्ट कोड के संबंधित हिस्से को देखकर देख सकते हैं :

    movzx   r13d, WORD PTR [rbp+rcx*2]
    movzx   eax,  WORD PTR [rbx+rcx*2]

    cmp     r13w, 478         ; (curr[i] < 479)
    ja      .L5

    cmp     ax, 478           ; (l[i + shift] < 479)
    ja      .L5

    add     r8d, 1            ; nontopOverlap++

आप यहाँ दो तुलनाएँ ( cmpनिर्देश) यहाँ देख रहे हैं, प्रत्येक के बाद एक अलग सशर्त कूद / शाखा ( jaया ऊपर कूदें)।

यह अंगूठे का एक सामान्य नियम है कि शाखाएं धीमी होती हैं और इसलिए उन्हें तंग छोरों से बचा जाना चाहिए। यह लगभग सभी x86 प्रोसेसर पर सच है, विनम्र 8088 से (जिनकी धीमी गति के समय और अत्यंत छोटी प्रीफ़ेच कतार [एक अनुदेश कैश के बराबर है), शाखा भविष्यवाणी की पूरी कमी के साथ संयुक्त, का मतलब है कि शाखाओं को कैश डंप होने की आवश्यकता थी ) से आधुनिक कार्यान्वयन (जिनकी लंबी पाइपलाइन गलत शाखाओं को समान रूप से महंगा बनाती हैं)। ध्यान दें कि मैं वहां फिसल गया था। पेंटियम प्रो के बाद से आधुनिक प्रोसेसर में उन्नत शाखा पूर्वानुमान इंजन हैं जो शाखाओं की लागत को कम करने के लिए डिज़ाइन किए गए हैं। यदि शाखा की दिशा का सही अनुमान लगाया जा सकता है, तो लागत न्यूनतम है। ज्यादातर समय, यह अच्छी तरह से काम करता है, लेकिन यदि आप रोग संबंधी मामलों में आते हैं, जहां शाखा भविष्यवक्ता आपकी तरफ नहीं है,आपका कोड बेहद धीमा हो सकता है । यह निश्चित रूप से आप यहाँ हैं, क्योंकि आप कहते हैं कि आपका सरणी अनसुलझा है।

आप कहते हैं कि बेंचमार्क ने पुष्टि की कि कोड के &&साथ बदलने *से कोड काफ़ी तेजी से होता है। इसका कारण तब स्पष्ट होता है जब हम ऑब्जेक्ट कोड के संबंधित हिस्से की तुलना करते हैं:

    movzx   r13d, WORD PTR [rbp+rcx*2]
    movzx   eax,  WORD PTR [rbx+rcx*2]

    xor     r15d, r15d        ; (curr[i] < 479)
    cmp     r13w, 478
    setbe   r15b

    xor     r14d, r14d        ; (l[i + shift] < 479)
    cmp     ax, 478
    setbe   r14b

    imul    r14d, r15d        ; meld results of the two comparisons

    cmp     r14d, 1           ; nontopOverlap++
    sbb     r8d, -1

यह थोड़ा जवाबी है कि यह तेजी से हो सकता है, क्योंकि यहां अधिक निर्देश हैं, लेकिन कभी-कभी अनुकूलन काम करता है। आप एक ही तुलना ( cmp) यहां कर रहे हैं, लेकिन अब, प्रत्येक एक से पहले है xorऔर एक द्वारा पीछा किया जा रहा है setbe। XOR एक रजिस्टर को साफ़ करने के लिए एक मानक चाल है। setbeएक x86 निर्देश है कि एक ध्वज के मूल्य पर आधारित एक सा सेट है, और अक्सर शाखा कोड लागू करने के लिए प्रयोग किया जाता है। यहाँ, setbeका विलोम है ja। यदि तुलना नीचे-या-बराबर (चूंकि रजिस्टर पूर्व-शून्य था, तो यह 0 अन्यथा होगा), jaतो यह अपने गंतव्य रजिस्टर को 1 पर सेट करता है, जबकि तुलना के ऊपर होने पर शाखा दी जाती है। एक बार इन दो मूल्यों में प्राप्त किया गया है r15bऔरr14bरजिस्टर, वे एक साथ कई बार उपयोग किए जाते हैं imul। गुणन पारंपरिक रूप से एक धीमी गति से संचालन था, लेकिन यह आधुनिक प्रोसेसर पर बहुत तेज़ है, और यह विशेष रूप से तेज़ होगा, क्योंकि यह केवल दो बाइट के आकार को गुणा कर रहा है।

आप बिटविंड और ऑपरेटर ( &) के साथ गुणा को आसानी से बदल सकते हैं , जो शॉर्ट-सर्किट मूल्यांकन नहीं करता है। यह कोड को अधिक स्पष्ट बनाता है, और एक ऐसा पैटर्न है जो आम तौर पर पहचानने वाले को संकलित करता है। लेकिन जब आप अपने कोड के साथ ऐसा करते हैं और इसे जीसीसी 5.4 के साथ संकलित करते हैं, तो यह पहली शाखा का उत्सर्जन जारी रखता है:

    movzx   r13d, WORD PTR [rbp+rcx*2]
    movzx   eax,  WORD PTR [rbx+rcx*2]

    cmp     r13w, 478         ; (curr[i] < 479)
    ja      .L4

    cmp     ax, 478           ; (l[i + shift] < 479)
    setbe   r14b

    cmp     r14d, 1           ; nontopOverlap++
    sbb     r8d, -1

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

संकलक (और अन्य संकलक, जैसे क्लैंग) की नई पीढ़ी इस नियम को जानती है, और कभी-कभी इसका उपयोग उसी कोड को उत्पन्न करने के लिए करेगी जिसे आपने हाथ से अनुकूलन करके मांगा होगा। मैं नियमित रूप से क्लैंग अनुवाद के &&भावों को उसी कोड में देखता हूं जो अगर मैंने उपयोग किया होता तो उत्सर्जित हो जाता &। सामान्य &&ऑपरेटर का उपयोग करके आपके कोड के साथ GCC 6.2 से प्रासंगिक आउटपुट निम्न है :

    movzx   r13d, WORD PTR [rbp+rcx*2]
    movzx   eax,  WORD PTR [rbx+rcx*2]

    cmp     r13d, 478         ; (curr[i] < 479)
    jg      .L7

    xor     r14d, r14d        ; (l[i + shift] < 479)
    cmp     eax, 478
    setle   r14b

    add     esi, r14d         ; nontopOverlap++

ध्यान दें कि यह कितना चालाक है! यह हस्ताक्षर किए शर्तों का उपयोग किया जाता है ( jgऔर setle) के रूप में अहस्ताक्षरित की स्थिति (करने का विरोध किया jaऔर setbe), लेकिन यह महत्वपूर्ण नहीं है। आप देख सकते हैं कि यह अभी भी पुराने संस्करण की तरह पहली स्थिति के लिए तुलना-और-शाखा करता है, और setCCदूसरी स्थिति के लिए शाखाहीन कोड उत्पन्न करने के लिए एक ही निर्देश का उपयोग करता है , लेकिन यह वृद्धि कैसे करता है, इसमें बहुत अधिक कुशल है। । एक sbbऑपरेशन के लिए झंडे सेट करने की तुलना में एक दूसरा, निरर्थक तुलना करने के बजाय , यह उस ज्ञान का उपयोग करता है जो r14dया तो बिना किसी शर्त के इस मूल्य को जोड़ने के लिए 1 या 0 होगा nontopOverlap। यदि r14d0 है, तो जोड़ एक विकल्प नहीं है; अन्यथा, यह 1 जोड़ता है, ठीक उसी तरह जैसे यह करना है।

जीसीसी 6.2 वास्तव में बिटकॉइन ऑपरेटर की तुलना में शॉर्ट-सर्कुलेटिंग ऑपरेटर का उपयोग करते समय अधिक कुशल कोड का उत्पादन करता है :&&&

    movzx   r13d, WORD PTR [rbp+rcx*2]
    movzx   eax,  WORD PTR [rbx+rcx*2]

    cmp     r13d, 478         ; (curr[i] < 479)
    jg      .L6

    cmp     eax, 478          ; (l[i + shift] < 479)
    setle   r14b

    cmp     r14b, 1           ; nontopOverlap++
    sbb     esi, -1

शाखा और सशर्त सेट अभी भी हैं, लेकिन अब यह वेतन वृद्धि के कम चतुर तरीके से वापस लौटता है nontopOverlap। यह एक महत्वपूर्ण सबक है कि आपको अपने कंपाइलर को आउट-चालाक करने की कोशिश करते समय सावधान रहना चाहिए!

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

nontopOverlap += ((curr[i] < 479) & (l[i + shift] < 479));

ifयहाँ कोई बयान नहीं दिया गया है, और कंपाइलरों के विशाल बहुमत ने इसके लिए ब्रांचिंग कोड को छोड़ने के बारे में कभी नहीं सोचा होगा। जीसीसी कोई अपवाद नहीं है; सभी संस्करण निम्नलिखित के लिए कुछ समान उत्पन्न करते हैं:

    movzx   r14d, WORD PTR [rbp+rcx*2]
    movzx   eax,  WORD PTR [rbx+rcx*2]

    cmp     r14d, 478         ; (curr[i] < 479)
    setle   r15b

    xor     r13d, r13d        ; (l[i + shift] < 479)
    cmp     eax, 478
    setle   r13b

    and     r13d, r15d        ; meld results of the two comparisons
    add     esi, r13d         ; nontopOverlap++

यदि आप पिछले उदाहरणों के साथ अनुसरण कर रहे हैं, तो यह आपको बहुत परिचित होना चाहिए। दोनों तुलना एक शाखाहीन तरीके से की जाती है, मध्यवर्ती परिणाम andएक साथ संपादित होते हैं, और फिर यह परिणाम (जो या तो 0 या 1 होगा) को addसंपादित किया जाता है nontopOverlap। यदि आप शाखा रहित कोड चाहते हैं, तो यह वस्तुतः यह सुनिश्चित करेगा कि आप इसे प्राप्त करें।

जीसीसी 7 ने और भी स्मार्ट हो गया है। यह अब मूल कोड के रूप में उपरोक्त चाल के लिए लगभग समान कोड (निर्देशों के कुछ मामूली पुनर्विकास को छोड़कर) उत्पन्न करता है। तो, आपके प्रश्न का उत्तर, "संकलक इस तरह से व्यवहार क्यों करता है?" , शायद इसलिए कि वे परिपूर्ण नहीं हैं! वे सबसे इष्टतम कोड को संभव बनाने के लिए उत्तराधिकारियों का उपयोग करने की कोशिश करते हैं, लेकिन वे हमेशा सबसे अच्छा निर्णय नहीं लेते हैं। लेकिन कम से कम वे समय के साथ होशियार हो सकते हैं!

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

और आपके सवाल के लिए कि क्या यह कुछ प्रोग्रामर के लिए बाहर देखने की जरूरत है, जवाब लगभग निश्चित रूप से नहीं है, कुछ हॉट लूप्स को छोड़कर जो आप माइक्रो-ऑप्टिमाइज़ेशन के माध्यम से तेज करने की कोशिश कर रहे हैं। फिर, आप डिससैस के साथ बैठते हैं और इसे ट्वीक करने के तरीके ढूंढते हैं। और, जैसा कि मैंने पहले कहा, उन फैसलों को फिर से तैयार करने के लिए तैयार रहें जब आप संकलक के एक नए संस्करण के लिए अद्यतन करते हैं, क्योंकि यह या तो आपके मुश्किल कोड के साथ कुछ बेवकूफी कर सकता है, या हो सकता है कि उसने अपने अनुकूलन उत्तराधिकारियों को पर्याप्त बदल दिया हो ताकि आप वापस जा सकें अपने मूल कोड का उपयोग करने के लिए। अच्छी तरह से टिप्पणी करें!


3
खैर, वहाँ एक सार्वभौमिक "बेहतर" नहीं है। यह सब आपकी स्थिति पर निर्भर करता है, यही कारण है कि जब आप इस तरह के निम्न-स्तरीय प्रदर्शन अनुकूलन कर रहे होते हैं तो आपको बिल्कुल बेंचमार्क करना पड़ता है। जैसा कि मैंने उत्तर में बताया, यदि आप शाखा की भविष्यवाणी के खोने के आकार पर हैं, तो गलत शाखाएं आपके कोड को बहुत धीमा कर देंगी । अंतिम बिट कोड किसी भी शाखा का उपयोग नहीं करता है ( j*निर्देशों की अनुपस्थिति को नोट करें), इसलिए यह उस स्थिति में तेज़ होगा। [जारी रखा]
कोड़ी ग्रे


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

6
@ हर्किल मुझे ऐसा लगता है कि पूरा उत्तर उस प्रश्न पर बोलता है। आप सही हैं कि मैं वास्तव में स्पष्ट रूप से इसे बाहर नहीं बुलाता था, लेकिन ऐसा लग रहा था कि यह पहले से ही काफी लंबा था। :-) जो भी व्यक्ति पूरी बात पढ़ने के लिए समय लेता है, उसे उस बिंदु की पर्याप्त समझ हासिल करनी चाहिए। लेकिन अगर आपको लगता है कि कुछ गायब है, या अधिक स्पष्टीकरण की आवश्यकता है, तो कृपया इसे शामिल करने के लिए उत्तर को संपादित करने के बारे में परेशान न हों। कुछ लोगों को यह पसंद नहीं है, लेकिन मैं बिल्कुल बुरा नहीं मानता। मैंने इसके बारे में एक संक्षिप्त टिप्पणी जोड़ दी, साथ ही 8bittree द्वारा सुझाए गए अनुसार मेरे शब्दों को संशोधित किया।
कोड़ी ग्रे

2
हाह, पूरक के लिए धन्यवाद, @ग्रीन। मेरे पास सुझाव देने के लिए कुछ खास नहीं है। जैसा कि सब कुछ के साथ, आप कर, देखकर और अनुभव करके एक विशेषज्ञ बन जाते हैं। मैंने वह सब कुछ पढ़ा है जो मुझे x86 आर्किटेक्चर, ऑप्टिमाइज़ेशन, कंपाइलर इंटर्नल, और अन्य निम्न-स्तरीय सामानों के बारे में बताने पर मेरे हाथ लग सकता है, और मुझे अभी भी सब कुछ का केवल एक अंश पता है जिसे जानना है। सीखने का सबसे अच्छा तरीका यह है कि आप अपने हाथों को गंदे खोदें। लेकिन इससे पहले कि आप शुरू करने की उम्मीद कर सकें, आपको सी (या सी ++), पॉइंटर्स, असेंबली लैंग्वेज और अन्य सभी निम्न-स्तरीय बुनियादी बातों की एक ठोस समझ की आवश्यकता होगी।
कोड़ी ग्रे

23

एक महत्वपूर्ण बात यह है कि ध्यान दें

(curr[i] < 479) && (l[i + shift] < 479)

तथा

(curr[i] < 479) * (l[i + shift] < 479)

शब्दार्थ समतुल्य नहीं हैं! विशेष रूप से, यदि आपके पास कभी ऐसी स्थिति होती है जहां:

  • 0 <= iऔर i < curr.size()दोनों सत्य हैं
  • curr[i] < 479 गलत है
  • i + shift < 0या i + shift >= l.size()सच है

तब अभिव्यक्ति (curr[i] < 479) && (l[i + shift] < 479)एक अच्छी तरह से परिभाषित बूलियन मूल्य होने की गारंटी है। उदाहरण के लिए, यह एक विभाजन दोष का कारण नहीं बनता है।

हालांकि, इन परिस्थितियों में, अभिव्यक्ति (curr[i] < 479) * (l[i + shift] < 479)है अपरिभाषित व्यवहार ; यह है एक विभाजन गलती पैदा करने के लिए अनुमति दी।

इसका अर्थ है कि मूल कोड स्निपेट के लिए, उदाहरण के लिए, कंपाइलर केवल एक लूप नहीं लिख सकता है जो दोनों तुलना करता है और एक andऑपरेशन करता है , जब तक कि कंपाइलर यह भी साबित l[i + shift]नहीं कर सकता है कि कभी भी ऐसी स्थिति में सेगफॉल्ट का कारण नहीं होगा, जिसकी आवश्यकता नहीं है।

संक्षेप में, कोड का मूल टुकड़ा उत्तरार्द्ध की तुलना में अनुकूलन के लिए कम अवसर प्रदान करता है। (बेशक, संकलक अवसर को पहचानता है या नहीं, यह एक पूरी तरह से अलग सवाल है)

आप इसके बजाय मूल संस्करण को ठीक कर सकते हैं

bool t1 = (curr[i] < 479);
bool t2 = (l[i + shift] < 479);
if (t1 && t2) {
    // ...

यह! shift(और max) के मूल्य के आधार पर यहाँ UB है ...
Matthieu M.

18

&&ऑपरेटर शॉर्ट सर्किट मूल्यांकन लागू करता है। इसका मतलब यह है कि दूसरे ऑपरेंड का मूल्यांकन केवल तभी किया जाता है जब पहले वाला मूल्यांकन करता है true। यह निश्चित रूप से उस मामले में एक छलांग का परिणाम है।

आप इसे दिखाने के लिए एक छोटा सा उदाहरण बना सकते हैं:

#include <iostream>

bool f(int);
bool g(int);

void test(int x, int y)
{
  if ( f(x) && g(x)  )
  {
    std::cout << "ok";
  }
}

कोडांतरक आउटपुट यहां पाया जा सकता है

आप पहले उत्पन्न कॉल को देख सकते हैं f(x), फिर आउटपुट की जांच करते हैं और मूल्यांकन करते हैं g(x)कि यह कब हुआ था true। अन्यथा यह फ़ंक्शन को छोड़ देता है।

इसके बजाय "बूलियन" गुणन का उपयोग करना हर बार दोनों ऑपरेंड के मूल्यांकन को मजबूर करता है और इस तरह एक छलांग की आवश्यकता नहीं होती है।

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


1
आप क्यों कहते हैं कि गुणन हर बार दोनों ऑपरेंड के मूल्यांकन को मजबूर करता है? 0 * x = x * 0 = 0 x के मान की परवाह किए बिना। अनुकूलन के रूप में, कंपाइलर गुणा के रूप में "शॉर्टक्रिसिट" कर सकता है। उदाहरण के लिए stackoverflow.com/questions/8145894/… देखें । इसके अलावा, &&ऑपरेटर के विपरीत , गुणा को पहले या दूसरे तर्क के साथ आलसी-मूल्यांकन किया जा सकता है, जो अनुकूलन के लिए अधिक स्वतंत्रता की अनुमति देता है।
SomeWittyUsername

@ जैन - "आम तौर पर शाखा की भविष्यवाणी मदद करती है, लेकिन अगर आपका डेटा यादृच्छिक है तो बहुत कुछ ऐसा नहीं है जिसकी भविष्यवाणी की जा सकती है।" - अच्छा जवाब देता है।
SChepurin

1
@SomeWittyUsername ठीक है, संकलक निस्संदेह किसी भी अनुकूलन को करने के लिए स्वतंत्र है जो अवलोकन योग्य व्यवहार रखता है। यह इसे रूपांतरित कर सकता है या संगणना छोड़ सकता है। यदि आप गणना करते हैं 0 * f()और fअवलोकन योग्य व्यवहार करते हैं, तो संकलक को इसे कॉल करना होगा। अंतर यह है कि शॉर्ट-सर्किट मूल्यांकन अनिवार्य है, &&लेकिन अनुमति दी जाती है यदि यह दिखाया जा सके कि यह इसके लिए बराबर है *
जेन्स

@SomeWittyUsername केवल उन मामलों में जिनमें 0 मान की भविष्यवाणी एक चर या स्थिर से की जा सकती है। मुझे लगता है कि ये मामले बहुत कम हैं। निश्चित रूप से ऑप्ट ओपी के मामले में अनुकूलन नहीं किया जा सकता है, क्योंकि सरणी पहुंच शामिल है।
डिएगो सेविला

3
@ लेंस: शॉर्ट-सर्किट मूल्यांकन अनिवार्य नहीं है। कोड केवल व्यवहार करने के लिए आवश्यक है जैसे कि यह शॉर्ट सर्किट; संकलक को किसी भी तरह से इसका उपयोग करने की अनुमति है कि वह परिणाम प्राप्त करना पसंद करता है।

-2

ऐसा इसलिए हो सकता है क्योंकि जब आप लॉजिकल ऑपरेटर का उपयोग कर रहे होते हैं &&तो कंपाइलर को स्टेटमेंट को सफल करने के लिए दो शर्तों की जांच करनी होती है। हालाँकि, दूसरे मामले में जब से आप एक बूल में एक इंट वैल्यू को परिवर्तित कर रहे हैं, कंपाइलर एकल छलांग की स्थिति के साथ (संभवतः) साथ में दिए जा रहे प्रकारों और मूल्यों के आधार पर कुछ धारणाएँ बनाता है। यह भी संभव है कि कंपाइलर बिट शिफ्ट्स के साथ jps को पूरी तरह से ऑप्टिमाइज़ कर दे।


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