क्यों स्विच को उसी तरह से ऑप्टिमाइज़ नहीं किया जाता है जैसे कि c / c ++ में और क्यों?


39

वर्ग का निम्नलिखित कार्यान्वयन सीएमपी / जेई बयानों की एक श्रृंखला का उत्पादन करता है जैसे कि मैं एक जंजीर की अपेक्षा करता हूं यदि कथन:

int square(int num) {
    if (num == 0){
        return 0;
    } else if (num == 1){
        return 1;
    } else if (num == 2){
        return 4;
    } else if (num == 3){
        return 9;
    } else if (num == 4){
        return 16;
    } else if (num == 5){
        return 25;
    } else if (num == 6){
        return 36;
    } else if (num == 7){
        return 49;
    } else {
        return num * num;
    }
}

और निम्नलिखित रिटर्न के लिए एक डेटा टेबल तैयार करता है:

int square_2(int num) {
    switch (num){
        case 0: return 0;
        case 1: return 1;
        case 2: return 4;
        case 3: return 9;
        case 4: return 16;
        case 5: return 25;
        case 6: return 36;
        case 7: return 49;
        default: return num * num;
    }
}

क्यों जीसीई एक में सबसे ऊपर एक को अनुकूलित करने में असमर्थ है?

संदर्भ के लिए विखंडन: https://godbolt.org/z/UP_igi

EDIT: दिलचस्प बात यह है कि स्विच केस के लिए MSVC डेटा टेबल की बजाय जंप टेबल बनाता है। और आश्चर्यजनक रूप से, क्लैंग उन्हें उसी परिणाम के लिए अनुकूलित करता है।


3
आपका क्या मतलब है "अपरिभाषित व्यवहार"? जब तक अवलोकनीय व्यवहार एक जैसा होता है, तब तक कंपाइलर जो भी असेंबली / मशीन कोड जेनरेट कर सकता है
bolov

2
@ user207421 की अनदेखी return; मामलों की संख्या नहीं है breaks, इस प्रकार स्विच में निष्पादन का एक विशिष्ट क्रम भी है। यदि / अन्यथा श्रृंखला में प्रत्येक शाखा में रिटर्न है, तो इस मामले में शब्दार्थ समतुल्य हैं। अनुकूलन असंभव नहीं है । एक counterexample icc किसी भी फंक्शन को ऑप्टिमाइज़ नहीं करता है।
user1810087

9
शायद सबसे सरल उत्तर ... gcc इस संरचना को देखने और इसे (अभी तक) अनुकूलित करने में सक्षम नहीं है।
user1810087

3
मैं @ user1810087 से सहमत हूं। आपको बस संकलक शोधन प्रक्रिया की वर्तमान सीमा मिली। एक उप-उप-मामला जिसे वर्तमान में अनुकूलन योग्य नहीं माना जाता है (कुछ संकलक द्वारा)। वास्तव में, प्रत्येक अन्य-अगर श्रृंखला को इस तरह से अनुकूलित नहीं किया जा सकता है, लेकिन केवल सबसेट जिसमें एसएएमई चर का निरंतर मूल्यों के खिलाफ परीक्षण किया जाता है।
रॉबर्टो कैबोनी

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

जवाबों:


29

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

अब आ रहा है if-else, यह ठीक विपरीत है। जबकि switch-caseलगातार समय में कार्यान्वित, शाखाओं की संख्या पर ध्यान दिए बिना, if-elseशाखाओं की एक छोटी संख्या के लिए अनुकूलित है। यहाँ, आप संकलक से अपेक्षा करेंगे कि आप मूल रूप से उस क्रम की तुलना उत्पन्न करें, जिस क्रम में आपने उन्हें लिखा है।

इसलिए यदि मैंने उपयोग किया था if-elseक्योंकि मैं उम्मीद करता हूं कि अधिकांश कॉल के square()लिए 0या 1शायद ही कभी अन्य मूल्यों के लिए हो, तो टेबल-लुकअप के लिए 'अनुकूलन' वास्तव में मेरे कोड को मेरी अपेक्षा से धीमा चलाने का कारण बन सकता है, ifबजाय इसके उपयोग के मेरे उद्देश्य को पराजित करना। एक की switch। इसलिए यद्यपि यह बहस का विषय है, मुझे लगता है कि जीसीसी सही काम कर रहा है और इसके अनुकूलन में क्लैंग अत्यधिक आक्रामक हो रहा है।

किसी व्यक्ति ने टिप्पणियों में, एक लिंक साझा किया था जहां क्लैंग यह अनुकूलन करता है और if-elseसाथ ही साथ लुकअप-टेबल आधारित कोड उत्पन्न करता है । कुछ उल्लेखनीय तब होता है जब हम क्लैंग के साथ मामलों की संख्या को केवल दो (और एक डिफ़ॉल्ट) तक कम कर देते हैं। यह एक बार फिर से अगर और स्विच दोनों के लिए समान कोड उत्पन्न करता है, लेकिन इस बार, दोनों के लिए लुकअप-टेबल दृष्टिकोण के बजाय तुलना और चाल पर स्विच करता है। इसका मतलब यह है कि स्विच-एहसान क्लैंग भी जानता है कि मामलों की संख्या छोटी होने पर 'अगर' पैटर्न अधिक इष्टतम है!

सारांश में, के लिए तुलना करने का एक क्रम if-elseऔर एक जंप-टेबल switch-caseमानक पैटर्न है जो कंपाइलर का पालन करते हैं और डेवलपर्स जब कोड लिखते हैं तो उम्मीद करते हैं। हालांकि, कुछ विशेष मामलों के लिए, कुछ संकलक इस पैटर्न को तोड़ने के लिए चुन सकते हैं जहां उन्हें लगता है कि यह बेहतर अनुकूलन प्रदान करता है। अन्य संकलक सिर्फ पैटर्न से चिपके रहने का विकल्प चुन सकते हैं, भले ही स्पष्ट रूप से उप-इष्टतम हो, डेवलपर को यह जानने के लिए भरोसा करना कि वह क्या चाहता है। दोनों अपने फायदे और नुकसान के साथ मान्य दृष्टिकोण हैं।


2
हां, अनुकूलन एक बहु-धारित तलवार है: वे क्या लिखते हैं, वे क्या चाहते हैं, उन्हें क्या मिलता है, और इसके लिए हम किसे शाप देते हैं।
डेडुप्लिकेटर

1
"... तब इसे टेबल-लुकअप में 'ऑप्टिमाइज़ करना' वास्तव में मेरे कोड को धीमा कर देगा क्योंकि मुझे उम्मीद है ..." क्या आप इसके लिए कोई औचित्य प्रदान कर सकते हैं? जंप टेबल कभी दो संभावित सशर्त शाखाओं की तुलना में धीमी क्यों होगी (इनपुट के खिलाफ 0और जांच करने के लिए 1)?
कोड़ी ग्रे

@ कोडिअरे मुझे कबूल करना है कि मुझे चक्र गिनने के स्तर तक नहीं मिला - मैं सिर्फ एक आंत-भावना से गया था कि एक सूचक के माध्यम से मेमोरी से लोड तुलना और कूद से अधिक चक्र ले सकता है, लेकिन मैं गलत हो सकता था। हालाँकि, मुझे आशा है कि आप मुझसे सहमत हैं कि इस मामले में भी, कम से कम '0' के लिए, ifस्पष्ट रूप से तेज है? अब, यहां एक मंच का एक उदाहरण है जहां ifस्विच का उपयोग करते समय 0 और 1 दोनों तेजी से होंगे : Godbolt.org/z/wcJhvS (ध्यान दें कि यहां खेलने के साथ-साथ कई अन्य अनुकूलन भी हैं)
th33lf

1
खैर, गिनती के चक्र आधुनिक सुपरकोलर OOO आर्किटेक्चर पर वैसे भी काम नहीं करते हैं। :-) स्मृति से भार गलत शाखाओं की तुलना में धीमा होने वाला नहीं है, इसलिए सवाल सिर्फ यह है कि शाखा की भविष्यवाणी होने की कितनी संभावना है? यह प्रश्न सशर्त शाखाओं के सभी तरीकों पर लागू होता है, चाहे स्पष्ट ifबयानों द्वारा या स्वचालित रूप से संकलक द्वारा उत्पन्न किया गया हो। मैं एक एआरएम विशेषज्ञ नहीं हूं, इसलिए मैं वास्तव में निश्चित नहीं हूं कि यदि आप जिस दावे के बारे switchमें बता रहे हैं ifवह सच होने की तुलना में तेज है। यह गलत शाखाओं के लिए दंड पर निर्भर करेगा, और यह वास्तव में किस एआरएम पर निर्भर करेगा ।
कोड़ी ग्रे

0

एक संभव तर्क यह है कि यदि निम्न मान हैं num अधिक होने की संभावना है, उदाहरण के लिए हमेशा 0, पहले वाले के लिए उत्पन्न कोड तेज हो सकता है। स्विच के लिए उत्पन्न कोड सभी मानों के लिए समान समय लेता है।

इस तालिका के अनुसार सर्वश्रेष्ठ मामलों की तुलना करें । तालिका के स्पष्टीकरण के लिए यह उत्तर देखें ।

अगर num == 0 , "if" के लिए आपके पास xor, test, je (जंप के साथ) है, तो रिटायर करें। विलंबता: 1 + 1 + कूद। हालांकि, एक्सोर और परीक्षण स्वतंत्र हैं, इसलिए वास्तविक निष्पादन की गति 1 + 1 चक्र से अधिक तेज होगी।

अगर num < 7 , "स्विच" के लिए आपके पास mov, cmp, ja (बिना जंप), मूव, रिट है। विलंबता: 2 + 1 + कोई कूद + 2।

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


1
हम्म, दिलचस्प सिद्धांत, लेकिन अगर बनाम बनाम आपके पास है: xor, test, jmp vs mov, cmp jmp। आखिरी छलांग के साथ तीन निर्देश। सबसे अच्छा मामले में बराबर लगता है, नहीं?
chacham15

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