जीसीसी लगभग समान सी कोड के लिए इस तरह की अलग-अलग विधानसभा क्यों उत्पन्न करता है?


184

एक अनुकूलित ftolफ़ंक्शन लिखते समय मुझे कुछ बहुत ही अजीब व्यवहार मिला GCC 4.6.1। मैं आपको पहले कोड दिखाऊंगा (स्पष्टता के लिए मैंने अंतर चिह्नित किया है):

fast_trunc_one, C:

int fast_trunc_one(int i) {
    int mantissa, exponent, sign, r;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);
    sign = i & 0x80000000;

    if (exponent < 0) {
        r = mantissa << -exponent;                       /* diff */
    } else {
        r = mantissa >> exponent;                        /* diff */
    }

    return (r ^ -sign) + sign;                           /* diff */
}

fast_trunc_two, C:

int fast_trunc_two(int i) {
    int mantissa, exponent, sign, r;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);
    sign = i & 0x80000000;

    if (exponent < 0) {
        r = (mantissa << -exponent) ^ -sign;             /* diff */
    } else {
        r = (mantissa >> exponent) ^ -sign;              /* diff */
    }

    return r + sign;                                     /* diff */
}

एक ही सही लगता है? खैर जीसीसी असहमत है। gcc -O3 -S -Wall -o test.s test.cइस के साथ संकलन के बाद विधानसभा उत्पादन है:

fast_trunc_one, उत्पन्न:

_fast_trunc_one:
LFB0:
    .cfi_startproc
    movl    4(%esp), %eax
    movl    $150, %ecx
    movl    %eax, %edx
    andl    $8388607, %edx
    sarl    $23, %eax
    orl $8388608, %edx
    andl    $255, %eax
    subl    %eax, %ecx
    movl    %edx, %eax
    sarl    %cl, %eax
    testl   %ecx, %ecx
    js  L5
    rep
    ret
    .p2align 4,,7
L5:
    negl    %ecx
    movl    %edx, %eax
    sall    %cl, %eax
    ret
    .cfi_endproc

fast_trunc_two, उत्पन्न:

_fast_trunc_two:
LFB1:
    .cfi_startproc
    pushl   %ebx
    .cfi_def_cfa_offset 8
    .cfi_offset 3, -8
    movl    8(%esp), %eax
    movl    $150, %ecx
    movl    %eax, %ebx
    movl    %eax, %edx
    sarl    $23, %ebx
    andl    $8388607, %edx
    andl    $255, %ebx
    orl $8388608, %edx
    andl    $-2147483648, %eax
    subl    %ebx, %ecx
    js  L9
    sarl    %cl, %edx
    movl    %eax, %ecx
    negl    %ecx
    xorl    %ecx, %edx
    addl    %edx, %eax
    popl    %ebx
    .cfi_remember_state
    .cfi_def_cfa_offset 4
    .cfi_restore 3
    ret
    .p2align 4,,7
L9:
    .cfi_restore_state
    negl    %ecx
    sall    %cl, %edx
    movl    %eax, %ecx
    negl    %ecx
    xorl    %ecx, %edx
    addl    %edx, %eax
    popl    %ebx
    .cfi_restore 3
    .cfi_def_cfa_offset 4
    ret
    .cfi_endproc

यह एक चरम अंतर है। यह वास्तव में प्रोफ़ाइल पर भी दिखाता है, fast_trunc_oneकी तुलना में लगभग 30% तेज है fast_trunc_two। अब मेरा सवाल: यह क्या कारण है?


1
परीक्षण के प्रयोजनों के लिए मैंने यहां एक जिस्ट बनाया है जहां आप आसानी से स्रोत को कॉपी / पेस्ट कर सकते हैं और देख सकते हैं कि क्या आप जीसीसी के अन्य सिस्टम / संस्करणों पर बग को पुन: पेश कर सकते हैं।
१०:१२ बजे

12
परीक्षण मामलों को स्वयं की निर्देशिका में रखें। उन्हें संकलित करें -S -O3 -da -fdump-tree-all। यह मध्यवर्ती प्रतिनिधित्व के कई स्नैपशॉट बनाएगा। उनके माध्यम से चलें (वे क्रमांकित हैं) कंधे से कंधा मिलाकर चल रहे हैं और आपको पहले मामले में लापता अनुकूलन खोजने में सक्षम होना चाहिए।
zwol

1
सुझाव दो: सभी intको बदलो unsigned intऔर देखो कि क्या अंतर गायब हो जाता है।
zwol

5
ऐसा लगता है कि दो कार्य थोड़ा अलग गणित कर रहे हैं। जबकि परिणाम समान हो सकते हैं, अभिव्यक्ति (r + shifted) ^ signसमान नहीं है r + (shifted ^ sign)। मुझे लगता है कि आशावादी को भ्रमित कर रहा है? FWIW, MSVC 2010 (16.00.40219.01) लिस्टिंग का उत्पादन करता है जो लगभग एक दूसरे के समान होते हैं: gist.github.com/2430454
DC26

1
@ डांसर: अरे धिक्कार है! मुझे वह जगह नहीं मिली। हालांकि यह अंतर के लिए स्पष्टीकरण नहीं है। मुझे एक नए संस्करण के साथ प्रश्न को अद्यतन करने दें जहां इसे खारिज किया जाता है।
orlp

जवाबों:


256

ओपी के संपादन के साथ सिंक करने के लिए अपडेट किया गया

कोड के साथ छेड़छाड़ करके, मैं यह देखने में कामयाब रहा कि जीसीसी पहले मामले को कैसे अनुकूलित करता है।

इससे पहले कि हम समझ सकें कि वे इतने अलग क्यों हैं, पहले हमें यह समझना चाहिए कि जीसीसी कैसे अनुकूलन करता है fast_trunc_one()

मानो या न मानो, fast_trunc_one()इस के लिए अनुकूलित किया जा रहा है:

int fast_trunc_one(int i) {
    int mantissa, exponent;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);

    if (exponent < 0) {
        return (mantissa << -exponent);             /* diff */
    } else {
        return (mantissa >> exponent);              /* diff */
    }
}

यह मूल fast_trunc_one()- रजिस्टर नाम और सब कुछ के समान सटीक विधानसभा का उत्पादन करता है ।

ध्यान दें कि xorविधानसभा के लिए कोई एस नहीं हैं fast_trunc_one()। यही कारण है कि यह मेरे लिए दूर दे दिया।


ऐसा कैसे?


चरण 1: sign = -sign

सबसे पहले, signचर पर एक नज़र डालते हैं । जबसेsign = i & 0x80000000; , केवल दो संभावित मान हो signसकते हैं:

  • sign = 0
  • sign = 0x80000000

अब, समझते हैं कि दोनों ही मामलों में sign == -sign। इसलिए, जब मैं इसके लिए मूल कोड बदलता हूं:

int fast_trunc_one(int i) {
    int mantissa, exponent, sign, r;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);
    sign = i & 0x80000000;

    if (exponent < 0) {
        r = mantissa << -exponent;
    } else {
        r = mantissa >> exponent;
    }

    return (r ^ sign) + sign;
}

यह मूल के समान सटीक विधानसभा का उत्पादन करता है fast_trunc_one()। मैं आपको विधानसभा को छोड़ दूंगा, लेकिन यह समान है - रजिस्टर नाम और सभी।


चरण 2: गणितीय कमी:x + (y ^ x) = y

sign केवल दो में से एक मान ले सकते हैं, 0 या 0x80000000

  • जब x = 0, तब x + (y ^ x) = yतुच्छ धारण करता है।
  • द्वारा जोड़ना और एक्सरे 0x80000000करना एक ही है। यह साइन बिट को फ्लिप करता है। इसलियेx + (y ^ x) = y भी रखती है x = 0x80000000

इसलिए, को x + (y ^ x)कम कर देता हैy । और कोड इसके लिए सरल हो जाता है:

int fast_trunc_one(int i) {
    int mantissa, exponent, sign, r;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);
    sign = i & 0x80000000;

    if (exponent < 0) {
        r = (mantissa << -exponent);
    } else {
        r = (mantissa >> exponent);
    }

    return r;
}

फिर, यह ठीक उसी विधानसभा के लिए संकलित करता है - रजिस्टर नाम और सभी।


यह उपरोक्त संस्करण अंत में इसे कम करता है:

int fast_trunc_one(int i) {
    int mantissa, exponent;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);

    if (exponent < 0) {
        return (mantissa << -exponent);             /* diff */
    } else {
        return (mantissa >> exponent);              /* diff */
    }
}

जो कि जीसीसी विधानसभा में वास्तव में बहुत कुछ है।


तो संकलक अनुकूलन क्यों नहीं करता है fast_trunc_two() एक ही चीज़ के लिए है?

अनुकूलन में महत्वपूर्ण हिस्सा fast_trunc_one()है x + (y ^ x) = y। मेंfast_trunc_two()x + (y ^ x) अभिव्यक्ति शाखा में विभाजित किया जा रहा है।

मुझे संदेह है कि यह अनुकूलन नहीं करने के लिए जीसीसी को भ्रमित करने के लिए पर्याप्त हो सकता है। (इसे ^ -signशाखा से बाहर फहराने और इसमें विलय करने की आवश्यकता होगीr + sign अंत ।)

उदाहरण के लिए, यह उसी विधानसभा का निर्माण करता है जैसे fast_trunc_one():

int fast_trunc_two(int i) {
    int mantissa, exponent, sign, r;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);
    sign = i & 0x80000000;

    if (exponent < 0) {
        r = ((mantissa << -exponent) ^ -sign) + sign;             /* diff */
    } else {
        r = ((mantissa >> exponent) ^ -sign) + sign;              /* diff */
    }

    return r;                                     /* diff */
}

4
संपादित करें, ऐसा लगता है कि मैंने दो संशोधन किए हैं। वर्तमान संशोधन ने दो उदाहरणों को छोड़ दिया और कोड को थोड़ा बदल दिया ... यह भ्रामक है।
18

2
@ नाइटक्रैकर कोई चिंता नहीं है। मैंने वर्तमान संस्करण के साथ सिंक करने के लिए अपना उत्तर अपडेट कर दिया है।
मिस्ट्रीकल

1
@ मूल: आपका अंतिम कथन अब नए संस्करण के साथ सही नहीं है, जिससे आपका उत्तर शून्य हो जाता है (यह सबसे महत्वपूर्ण प्रश्न का उत्तर नहीं देता है, "जीसीसी इस तरह की अलग-अलग विधानसभा क्यों उत्पन्न करता है" )
orlp

11
उत्तर फिर से अपडेट किया गया। मुझे यकीन नहीं है कि यह पर्याप्त संतोषजनक है। लेकिन मुझे नहीं लगता कि मैं यह जानने के बिना बहुत बेहतर कर सकता हूं कि प्रासंगिक जीसीसी अनुकूलन कैसे काम करता है।
मिस्टिकल

4
@ मूल: कड़ाई से बोलना, जब तक हस्ताक्षर किए गए प्रकार का गलत तरीके से इस कोड में उपयोग किया जा रहा है, बहुत सारे कंपाइलर यहां बना रहे हैं, ऐसे मामलों में हैं जहां व्यवहार अपरिभाषित है ...
R .. GitHubPOP मदद करना आईसीई

63

यह संकलक की प्रकृति है। यह मानते हुए कि वे सबसे तेज या सबसे अच्छा रास्ता अपनाएंगे, काफी गलत है। किसी को भी, जिसका अर्थ है कि आपके कोड को अनुकूलित करने के लिए आपको कुछ भी करने की आवश्यकता नहीं है क्योंकि "आधुनिक संकलक" रिक्त में भरते हैं, सबसे अच्छा काम करते हैं, सबसे तेज़ कोड बनाते हैं, आदि। वास्तव में मैंने देखा कि gcc 3.x से भी बदतर हो गया है। कम से कम हाथ पर 4.x। 4.x इस बिंदु से 3.x तक पकड़ा जा सकता है, लेकिन इसके आरंभ में यह धीमा कोड है। अभ्यास के साथ आप सीख सकते हैं कि अपना कोड कैसे लिखें ताकि कंपाइलर को अधिक मेहनत न करनी पड़े और परिणामस्वरूप अधिक सुसंगत और अपेक्षित परिणाम मिलें।

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

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

क्या आपने gcc के विभिन्न संस्करणों को देखा? 3.x और 4.x विशेष रूप से 4.5 बनाम 4.6 बनाम 4.7, आदि। और अलग लक्ष्य प्रोसेसर, x86, बांह, कूल्हों, आदि या x86 के विभिन्न स्वादों के लिए यदि वह मूल संकलक है जिसका आप उपयोग करते हैं, 32 बिट बनाम 64 बिट, आदि? और फिर विभिन्न लक्ष्यों के लिए llvm (क्लैंग)?

रहस्यवादी ने कोड का विश्लेषण / अनुकूलन करने की समस्या के माध्यम से काम करने के लिए आवश्यक विचार प्रक्रिया में एक उत्कृष्ट काम किया है, एक कंपाइलर से उम्मीद है कि किसी भी "आधुनिक संकलक" से अपेक्षित नहीं है।

गणित के गुणों में शामिल हुए बिना, इस फॉर्म का कोड

if (exponent < 0) {
  r = mantissa << -exponent;                       /* diff */
} else {
  r = mantissa >> exponent;                        /* diff */
}
return (r ^ -sign) + sign;                           /* diff */

कंपाइलर को A तक ले जाने वाला है: इसे उस रूप में लागू करें, अगर-तब-तब प्रदर्शन करें और फिर खत्म होने और लौटने के लिए कॉमन कोड पर कंवर्ट करें। या B: फ़ंक्शन के टेल एंड के बाद से एक शाखा को बचाएं। आर का उपयोग या बचत के साथ भी परेशान न करें।

if (exponent < 0) {
  return((mantissa << -exponent)^-sign)+sign;
} else {
  return((mantissa << -exponent)^-sign)+sign;
}

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

यह gcc सोर्स कोड में खुदाई करने का एक सही मौका है। ऐसा प्रतीत होता है कि आपको एक ऐसा मामला मिल गया है, जहां आशावादी ने एक मामले में एक चीज देखी, फिर किसी अन्य मामले में दूसरी चीज। फिर अगला कदम उठाएं और देखें कि क्या आप उस मामले को देखने के लिए नहीं मिल सकते हैं। प्रत्येक अनुकूलन वहाँ है क्योंकि कुछ व्यक्ति या समूह ने अनुकूलन को मान्यता दी है और जानबूझकर इसे वहां रखा है। इस अनुकूलन के होने और हर बार काम करने के लिए किसी को इसे वहां रखना होगा (और फिर इसका परीक्षण करें, और फिर इसे भविष्य में बनाए रखें)।

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

लब्बोलुआब यह है कि आप एक संकलक अलग स्रोत खिलाया और एक ही परिणाम की उम्मीद है। समस्या कंपाइलर आउटपुट नहीं बल्कि उपयोगकर्ता की अपेक्षाएं हैं। एक विशेष संकलक और प्रोसेसर के लिए प्रदर्शित करना काफी आसान है, कोड की एक पंक्ति के अलावा जो पूरे फ़ंक्शन को नाटकीय रूप से धीमा बनाता है। उदाहरण के लिए = a + b + 2 क्यों बदल रहा है; to a = b + c + 2; कारण _fill_in_the_blank_compiler_name_ मौलिक रूप से अलग और धीमा कोड उत्पन्न करता है? संकलक होने के पाठ्यक्रम का उत्तर इनपुट पर अलग-अलग कोड दिया गया था, इसलिए यह कंपाइलर के लिए अलग आउटपुट उत्पन्न करने के लिए पूरी तरह से मान्य है। (इससे भी बेहतर है जब आप कोड की दो असंबद्ध लाइनों को स्वैप करते हैं और आउटपुट को नाटकीय रूप से बदलते हैं) आउटपुट की जटिलता और आकार के इनपुट की जटिलता और आकार के बीच कोई अपेक्षित संबंध नहीं है।

for(ra=0;ra<20;ra++) dummy(ra);

यह कोडांतरक की 60-100 लाइनों के बीच कहीं उत्पन्न हुआ। इसने लूप को अनियंत्रित कर दिया। मैंने लाइनों की गिनती नहीं की, यदि आप इसके बारे में सोचते हैं, तो इसे फ़ंक्शन कॉल में इनपुट पर परिणाम को कॉपी करना होगा, फ़ंक्शन कॉल करना होगा, तीन ऑपरेशन न्यूनतम करना होगा। इसलिए लक्ष्य पर निर्भर करता है कि शायद 60 निर्देश कम से कम, 80 अगर चार प्रति लूप, 100 अगर पांच प्रति लूप, आदि।


आपने अपना जवाब क्यों दिया? ओड के रूप में अच्छी तरह से संपादित के साथ असहमत लग रहा था; ;-)।
पीटर -

@ पीटरए.साइडर अपने सभी उत्तरों से लगता है कि उसी तारीख को बर्बरता की गई थी। मुझे लगता है कि किसी ने उसके (चोरी किए हुए) अकाउंट डेटा के साथ ऐसा किया।
ट्रिनिटी ४२०

23

रहस्यवादी ने पहले से ही एक महान विवरण दिया है, लेकिन मुझे लगा कि मैं एफडब्ल्यूआईडब्ल्यू जोड़ूंगा, कि वास्तव में कुछ भी मौलिक नहीं है कि एक कंपाइलर एक के लिए अनुकूलन क्यों करेगा और दूसरे के लिए नहीं।

clangउदाहरण के लिए, LLVM का संकलक, दोनों फ़ंक्शन (फ़ंक्शन नाम को छोड़कर) के लिए समान कोड देता है:

_fast_trunc_two:                        ## @fast_trunc_one
        movl    %edi, %edx
        andl    $-2147483648, %edx      ## imm = 0xFFFFFFFF80000000
        movl    %edi, %esi
        andl    $8388607, %esi          ## imm = 0x7FFFFF
        orl     $8388608, %esi          ## imm = 0x800000
        shrl    $23, %edi
        movzbl  %dil, %eax
        movl    $150, %ecx
        subl    %eax, %ecx
        js      LBB0_1
        shrl    %cl, %esi
        jmp     LBB0_3
LBB0_1:                                 ## %if.then
        negl    %ecx
        shll    %cl, %esi
LBB0_3:                                 ## %if.end
        movl    %edx, %eax
        negl    %eax
        xorl    %esi, %eax
        addl    %edx, %eax
        ret

यह कोड ओपी के पहले gcc संस्करण जितना छोटा नहीं है, लेकिन दूसरा जितना लंबा नहीं है।

एक अन्य संकलक (जिसे मैं नाम नहीं दूंगा) से कोड, x86_64 के लिए संकलन, दोनों कार्यों के लिए इसे तैयार करता है:

fast_trunc_one:
        movl      %edi, %ecx        
        shrl      $23, %ecx         
        movl      %edi, %eax        
        movzbl    %cl, %edx         
        andl      $8388607, %eax    
        negl      %edx              
        orl       $8388608, %eax    
        addl      $150, %edx        
        movl      %eax, %esi        
        movl      %edx, %ecx        
        andl      $-2147483648, %edi
        negl      %ecx              
        movl      %edi, %r8d        
        shll      %cl, %esi         
        negl      %r8d              
        movl      %edx, %ecx        
        shrl      %cl, %eax         
        testl     %edx, %edx        
        cmovl     %esi, %eax        
        xorl      %r8d, %eax        
        addl      %edi, %eax        
        ret                         

जो इसमें आकर्षक है कि यह दोनों पक्षों की गणना करता है if और फिर दाईं ओर चुनने के लिए अंत में एक सशर्त चाल का उपयोग करता है।

Open64 संकलक निम्नलिखित पैदा करता है:

fast_trunc_one: 
    movl %edi,%r9d                  
    sarl $23,%r9d                   
    movzbl %r9b,%r9d                
    addl $-150,%r9d                 
    movl %edi,%eax                  
    movl %r9d,%r8d                  
    andl $8388607,%eax              
    negl %r8d                       
    orl $8388608,%eax               
    testl %r8d,%r8d                 
    jl .LBB2_fast_trunc_one         
    movl %r8d,%ecx                  
    movl %eax,%edx                  
    sarl %cl,%edx                   
.Lt_0_1538:
    andl $-2147483648,%edi          
    movl %edi,%eax                  
    negl %eax                       
    xorl %edx,%eax                  
    addl %edi,%eax                  
    ret                             
    .p2align 5,,31
.LBB2_fast_trunc_one:
    movl %r9d,%ecx                  
    movl %eax,%edx                  
    shll %cl,%edx                   
    jmp .Lt_0_1538                  

और समान, लेकिन समान नहीं, के लिए कोड fast_trunc_two

वैसे भी, जब अनुकूलन की बात आती है, तो यह एक लॉटरी है - यह वही है जो यह है ... यह जानना हमेशा आसान नहीं होता है कि आप किसी विशेष तरीके से कोड क्यों बनाते हैं।


10
संकलक क्या आप कुछ शीर्ष-गुप्त सुपरकंपलर का नाम नहीं देंगे?
orlp

4
शीर्ष गुप्त संकलक शायद इंटेल है icc। मेरे पास केवल 32-बिट संस्करण है, लेकिन यह बहुत समान कोड का उत्पादन करता है।
Janus Troelsen

5
मेरा मानना ​​है कि यह आईसीसी है। कंपाइलर जानता है कि प्रोसेसर निर्देश स्तर समानता के लिए सक्षम है और इस प्रकार दोनों शाखाओं को एक साथ गणना की जा सकती है। झूठी शाखा की भविष्यवाणी के ओवरहेड की तुलना में सशर्त चाल का ओवरहेड बहुत कम है।
नवपर्व
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.