क्या कंपाइलर अन्य प्रकार के लूप्स के लिए डू-जबकि लूप के लिए बेहतर कोड का उत्पादन करते हैं?


89

वहाँ zlib सम्पीडन पुस्तकालय (जो कई अन्य लोगों के बीच क्रोमियम परियोजना में उपयोग किया जाता है ) में एक टिप्पणी है, जिसका अर्थ है कि सी में एक करते समय लूप अधिकांश संकलक पर "बेहतर" कोड उत्पन्न करता है। यहां कोड का स्निपेट है जहां यह दिखाई देता है।

do {
} while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
         *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
         *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
         *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
         scan < strend);
/* The funny "do {}" generates better code on most compilers */

https://code.google.com/p/chromium/codesearch#chromium/src/third_party/zlib/deflate.c&l=1225

क्या कोई प्रमाण है कि अधिकांश (या कोई) संकलक बेहतर (जैसे अधिक कुशल) कोड उत्पन्न करेगा?

अपडेट: मूल लेखक में से एक, मार्क एडलर ने टिप्पणियों में थोड़ा सा संदर्भ दिया


7
वैसे, स्पष्ट करने के लिए, यह क्रोमियम का हिस्सा नहीं है। जैसा कि आप URL से कटौती कर सकते हैं, यह एक "3-पार्टी" परियोजना है, और यदि आप इसे और भी करीब से देखते हैं, तो आप महसूस कर सकते हैं कि यह कोड ZLib से है, जो कि व्यापक रूप से इस्तेमाल किया जाने वाला, सामान्य-उद्देश्य संपीड़न पुस्तकालय है।

1
The funny "do {}" generates better code--- इससे बेहतर क्या? मज़ाक करते हुए () या उबाऊ की तुलना में, नियमित रूप से {}?
एन। 'सर्वनाम' मी।

@ H2CO3 स्पष्टीकरण के लिए धन्यवाद, मैंने मूल के बारे में अधिक विशिष्ट होने के लिए प्रश्न को संपादित किया है।
डेनिस

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

जवाबों:


108

सबसे पहले:

एक do-whileपाश एक के समान नहीं है while-loop या एक for-loop।

  • while तथा for लूप्स लूप बॉडी को बिल्कुल नहीं चला सकते हैं।
  • एक do-whileलूप हमेशा लूप बॉडी को कम से कम एक बार चलाता है - यह प्रारंभिक स्थिति की जांच को छोड़ देता है।

तो यह तार्किक अंतर है। कहा कि, हर कोई इसका कड़ाई से पालन नहीं करता है। यह बहुत आम था whileया forछोरों तब भी जब यह है कि वह हमेशा पाश में कम से कम एक बार होगा की गारंटी है इस्तेमाल किया जा रहा। (विशेष रूप से फ़ॉरेस्ट लूप वाली भाषाओं में ।)

इसलिए सेब और संतरे की तुलना करने से बचने के लिए, मैं यह मानकर चलूंगा कि लूप हमेशा कम से कम एक बार चलेगा। इसके अलावा, मैं forफिर से छोरों का उल्लेख नहीं करूंगा क्योंकि वे अनिवार्य रूप से whileएक लूप काउंटर के लिए सिंटैक्स चीनी के एक बिट के साथ छोरों हैं ।

तो मैं इस सवाल का जवाब दूंगा:

यदि एक whileलूप को कम से कम एक बार लूप की गारंटी दी जाती है, तो क्या do-whileलूप का उपयोग करने से कोई प्रदर्शन लाभ होता है ।


एक do-whileपहली शर्त जांच को छोड़ देता है। इसलिए मूल्यांकन करने के लिए एक कम शाखा और एक कम स्थिति है।

यदि स्थिति जांचना महंगा है, और आप जानते हैं कि आपको कम से कम एक बार लूप की गारंटी है, तो एक do-whileलूप तेज हो सकता है।

और जबकि यह सबसे अच्छा में एक माइक्रो-ऑप्टिमाइज़ेशन माना जाता है, यह एक ऐसा है जो कंपाइलर हमेशा नहीं कर सकता है: विशेष रूप से जब कंपाइलर यह साबित करने में असमर्थ है कि लूप हमेशा कम से कम एक बार दर्ज करेगा।


दूसरे शब्दों में, एक समय-पाश:

while (condition){
    body
}

प्रभावी रूप से इस के रूप में ही है:

if (condition){
    do{
        body
    }while (condition);
}

यदि आप जानते हैं कि आप हमेशा कम से कम एक बार लूप करेंगे, कि अगर-स्टेटमेंट बाहरी है।


इसी तरह विधानसभा स्तर पर, यह लगभग अलग-अलग छोरों को कैसे संकलित किया जाता है:

लूप करते समय:

start:
    body
    test
    conditional jump to start

घुमाव के दौरान:

    test
    conditional jump to end
start:
    body
    test
    conditional jump to start
end:

ध्यान दें कि स्थिति को डुप्लिकेट किया गया है। एक वैकल्पिक तरीका है:

    unconditional jump to end
start:
    body
end:
    test
    conditional jump to start

... जो अतिरिक्त छलांग के लिए डुप्लिकेट कोड को हटा देता है।

किसी भी तरह से, यह अभी भी एक सामान्य do-whileलूप से भी बदतर है ।

उस ने कहा, कंपाइलर जो चाहे कर सकते हैं। और अगर वे साबित कर सकते हैं कि लूप हमेशा एक बार में प्रवेश करता है, तो यह आपके लिए काम करता है।


लेकिन सवाल में विशेष उदाहरण के लिए चीजें थोड़ी अजीब हैं क्योंकि इसमें एक खाली लूप बॉडी है। चूंकि कोई शरीर नहीं है, इसलिए whileऔर के बीच कोई तार्किक अंतर नहीं है do-while

FWIW, मैंने इसे विज़ुअल स्टूडियो 2012 में परीक्षण किया:

  • खाली शरीर के साथ, यह वास्तव में whileऔर के लिए एक ही कोड उत्पन्न करता है do-while। इसलिए यह हिस्सा पुराने दिनों का अवशेष है, जब कंपाइलर उतने महान नहीं थे।

  • लेकिन एक गैर-खाली निकाय के साथ, वीएस २०१२ हालत कोड के दोहराव से बचने का प्रबंधन करता है, लेकिन फिर भी एक अतिरिक्त सशर्त कूद पैदा करता है।

तो यह विडंबना है कि जबकि प्रश्न में उदाहरण पर प्रकाश डाला गया है कि do-whileसामान्य मामले में एक लूप क्यों तेज हो सकता है, उदाहरण खुद को आधुनिक संकलक पर कोई लाभ नहीं देता है।

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


12
तो हालत की जाँच कर रहा है एक कम समय ऐसे एक महान लाभ? मुझे बहुत अधिक शक है कि। लूप को 100 बार चलाएं और यह पूरी तरह से महत्वहीन हो जाता है।

7
@ H2CO3 लेकिन क्या होगा अगर लूप केवल एक या दो बार चलता है? और डुप्लिकेट स्थिति कोड से उस बढ़े हुए कोड-आकार के बारे में क्या?
रहस्यपूर्ण

6
@ मूल यदि एक लूप केवल एक या दो बार चलता है, तो वह लूप अनुकूलन योग्य नहीं है। और बढ़े हुए कोड का आकार ... एक ठोस तर्क नहीं है, सबसे अच्छा है। यह एक आवश्यकता नहीं है कि हर कंपाइलर इसे आपके द्वारा दिखाए गए तरीके से लागू करे। मैंने अपनी खुद की खिलौना भाषा के लिए एक कंपाइलर लिखा है , और लूप की शुरुआत के लिए बिना शर्त कूद के साथ लूप कार्यान्वित किया जाता है, इसलिए हालत के लिए कोड केवल एक बार उत्सर्जित होता है।

30
@ H2CO3 "यदि एक लूप केवल एक या दो बार चलता है, तो वह लूप अनुकूलन के लायक नहीं है।" - क्षमा करें मैं असहमत हूं। यह दूसरे लूप के अंदर हो सकता है। अपने स्वयं के अत्यधिक अनुकूलित एचपीसी कोड के टन इस तरह से हैं। और हां करते-करते फर्क पड़ता है।
रहस्यपूर्ण

29
@ H2CO3 मैंने कहाँ कहा कि मैं इसे प्रोत्साहित कर रहा था? प्रश्न पूछता है कि do-whileलूप थोड़ी देर की तुलना में तेज है। और मैंने प्रश्न का उत्तर यह कहकर दिया कि यह तेज हो सकता है। मैंने कितना नहीं कहा। मैंने यह नहीं कहा कि क्या यह सार्थक था। मैंने किसी को भी ऐसा करने की सलाह नहीं दी, जबकि वह लूप में परिवर्तित होना शुरू कर देता है। लेकिन केवल इस बात से इनकार करते हुए कि एक अनुकूलन की संभावना है, भले ही वह एक छोटा हो, मेरी राय में उन लोगों के प्रति असंतोष है जो देखभाल करते हैं और इन चीजों में रुचि रखते हैं।
रहस्यमयी नोवा

24

क्या कोई प्रमाण है कि अधिकांश (या कोई) संकलक बेहतर (जैसे अधिक कुशल) कोड उत्पन्न करेगा?

ज्यादा नहीं, जब तक आप कुछ के साथ एक विशिष्ट मंच पर एक वास्तविक, विशिष्ट संकलक की वास्तविक उत्पन्न विधानसभा को देखते हैं विशिष्ट ऑप्टिमाइज़ेशन सेटिंग्स के साथ न देखें।

यह शायद दशकों पहले (जब ZLib लिखा गया है) के बारे में चिंता करने योग्य था, लेकिन निश्चित रूप से आजकल नहीं, जब तक कि आप वास्तविक रूपरेखा द्वारा नहीं पाए जाते हैं , कि यह आपके कोड से एक अड़चन को हटा देता है।


9
अच्छी तरह से - वाक्यांश premature optimizationयहाँ दिमाग में आता है।
जेम्स स्नेल

@JamesSnell बिल्कुल और यह वही है जो शीर्ष रेटेड उत्तर का समर्थन करता है / प्रोत्साहित करता है।

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

16

संक्षेप में (tl; डॉ।):

मैं ओपी के कोड में टिप्पणी को थोड़ा अलग ढंग से व्याख्या कर रहा हूं, मुझे लगता है कि "बेहतर कोड" जो उन्होंने दावा किया है कि वास्तविक काम को लूप "स्थिति" में ले जाने के कारण था। हालांकि मैं पूरी तरह से सहमत हूं कि यह बहुत संकलक विशिष्ट है और यह कि उन्होंने तुलना की, जबकि थोड़ा अलग कोड का उत्पादन करने में सक्षम है, ज्यादातर व्यर्थ है और शायद अप्रचलित है, जैसा कि मैं नीचे दिखाता हूं।


विवरण:

यह कहना मुश्किल है कि इस do {} whileबेहतर कोड के निर्माण के बारे में उनकी टिप्पणी से मूल लेखक का क्या मतलब है , लेकिन मैं यहां जो उठाया गया था, उसकी तुलना में एक और दिशा में अटकल लगाना चाहता हूं - हम मानते हैं कि do {} whileऔर while {}छोरों के बीच का अंतर बहुत पतला है (एक कम शाखा के रूप में मिस्टिकल ने कहा), लेकिन इस कोड में "फन्नीयर" भी कुछ है और वह इस पागल स्थिति के अंदर सारा काम डाल रहा है, और आंतरिक भाग को खाली रख रहा है ( do {})।

मैंने निम्नलिखित कोड gcc 4.8.1 (-O3) पर आज़माया है, और यह एक दिलचस्प अंतर देता है -

#include "stdio.h" 
int main (){
    char buf[10];
    char *str = "hello";
    char *src = str, *dst = buf;

    char res;
    do {                            // loop 1
        res = (*dst++ = *src++);
    } while (res);
    printf ("%s\n", buf);

    src = str;
    dst = buf;
    do {                            // loop 2
    } while (*dst++ = *src++);
    printf ("%s\n", buf);

    return 0; 
}

संकलन के बाद -

00000000004003f0 <main>:
  ... 
; loop 1  
  400400:       48 89 ce                mov    %rcx,%rsi
  400403:       48 83 c0 01             add    $0x1,%rax
  400407:       0f b6 50 ff             movzbl 0xffffffffffffffff(%rax),%edx
  40040b:       48 8d 4e 01             lea    0x1(%rsi),%rcx
  40040f:       84 d2                   test   %dl,%dl
  400411:       88 16                   mov    %dl,(%rsi)
  400413:       75 eb                   jne    400400 <main+0x10>
  ...
;loop 2
  400430:       48 83 c0 01             add    $0x1,%rax
  400434:       0f b6 48 ff             movzbl 0xffffffffffffffff(%rax),%ecx
  400438:       48 83 c2 01             add    $0x1,%rdx
  40043c:       84 c9                   test   %cl,%cl
  40043e:       88 4a ff                mov    %cl,0xffffffffffffffff(%rdx)
  400441:       75 ed                   jne    400430 <main+0x40>
  ...

तो पहला लूप 7 निर्देश करता है जबकि दूसरा 6 करता है, भले ही वे एक ही काम करने वाले हों। अब, मैं वास्तव में नहीं बता सकता कि क्या इसके पीछे कुछ संकलक स्मार्टनेस है, शायद नहीं और यह सिर्फ संयोग है, लेकिन मैंने यह नहीं जांचा है कि यह अन्य संकलक विकल्पों के साथ कैसे इंटरैक्ट करता है, जो इस परियोजना का उपयोग कर रहा है।


दूसरी ओर क्लैंग 3.3 (-ओ 3) पर, दोनों लूप इस 5 निर्देश कोड को उत्पन्न करते हैं:

  400520:       8a 88 a0 06 40 00       mov    0x4006a0(%rax),%cl
  400526:       88 4c 04 10             mov    %cl,0x10(%rsp,%rax,1)
  40052a:       48 ff c0                inc    %rax
  40052d:       48 83 f8 05             cmp    $0x5,%rax
  400531:       75 ed                   jne    400520 <main+0x20>

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


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

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


ऐसा लगता है कि यह एक रजिस्टर चाल बचाता है। mov %rcx,%rsi:) मैं देख सकता हूँ कि कैसे कोडिंग कोडिंग कर सकता है।
रहस्यमयी नोव

@ मूल, हालांकि आप सूक्ष्म अनुकूलन के बारे में सही हैं। कभी-कभी एकल निर्देश को सहेजना भी किसी भी चीज के लायक नहीं होता है (और reg-to-reg चाल आज रीनेम करने के साथ लगभग मुफ्त होनी चाहिए)।
लीवर

ऐसा नहीं लगता है कि एएमडी बुलडोजर और इंटेल आइवी ब्रिज तक नाम बदलने का कदम लागू किया गया था। यह आश्चर्य की बात है!
रहस्यमयी नोव

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

3
ऐसा लगता है कि आपने मूल कोड में टिप्पणी की व्याख्या सबसे अधिक की है, और यह समझ में आता है। टिप्पणी कहती है, "मजेदार do {} .." लेकिन यह नहीं कहता कि गैर-मजाकिया संस्करण इसकी तुलना क्या करता है। अधिकांश लोगों को डू-जबकि और जबकि के बीच का अंतर पता है, इसलिए मेरा अनुमान है कि "मजेदार डो {}" उस पर लागू नहीं हुआ, लेकिन लूप-अनरोलिंग और / या अतिरिक्त असाइनमेंट की कमी के रूप में, जैसा कि आपने दिखाया यहाँ।
हाबिल

10

एक whileपाश अक्सर एक के रूप में संकलित किया गया है do-whileहालत, यानी के लिए एक प्रारंभिक शाखा के साथ पाश

    bra $1    ; unconditional branch to the condition
$2:
    ; loop body
$1:
    tst <condition> ; the condition
    brt $2    ; branch if condition true

जबकि do-whileलूप का संकलन प्रारंभिक शाखा के बिना समान है। आप इससे देख सकते हैं कि while()प्रारंभिक शाखा की लागत से स्वाभाविक रूप से कम कुशल है, जो कि केवल एक बार भुगतान किया जाता है। [लागू करने के भोले तरीके से तुलना करें while,जिसके लिए एक सशर्त शाखा और प्रति पुनरावृत्ति एक बिना शर्त शाखा की आवश्यकता होती है।]

कहा जा रहा है कि, वे वास्तव में तुलनीय विकल्प नहीं हैं। एक whileलूप को लूप में बदलना दर्दनाक है do-whileऔर इसके विपरीत। वे अलग-अलग काम करते हैं। और इस मामले में कई विधि कॉल पूरी तरह से हावी हो जाएंगे जो संकलक के whileखिलाफ किया थाdo-while.


7

टिप्पणी नियंत्रण कथन की पसंद के बारे में नहीं है (बनाम बनाम जबकि), यह लूप के अनियंत्रित होने के बारे में है !!!

जैसा कि आप देख सकते हैं, यह एक स्ट्रिंग तुलनात्मक फ़ंक्शन है (स्ट्रिंग तत्व संभवतः 2 बाइट्स लंबे होते हैं), जिसे शॉर्टकट-एक्सप्रेशन और अभिव्यक्ति में चार के बजाय एक एकल तुलना के साथ लिखा जा सकता था।

यह बाद का कार्यान्वयन निश्चित रूप से तेजी से होता है, क्योंकि यह प्रत्येक चार तत्व तुलनाओं के बाद एंड-ऑफ-स्ट्रिंग स्थिति का एक एकल चेक करता है, जबकि मानक कोडिंग में प्रति तुलना में एक चेक शामिल होगा। अलग-अलग कहा, 4 तत्व प्रति 5 परीक्षण बनाम 4 तत्व प्रति 8 परीक्षण।

वैसे भी, यह तभी काम करेगा जब स्ट्रिंग की लंबाई चार से अधिक हो या इसमें एक सेंटिनल तत्व हो (ताकि दोनों तारों को strendसीमा पार करने की गारंटी हो )। बहुत जोखिम भरा!


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

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

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

0

इस मामले की बनाम कार्यकुशलता की यह चर्चा इस मामले में पूरी तरह से निरर्थक है, क्योंकि कोई निकाय नहीं है।

while (Condition)
{
}

तथा

do
{
}
while (Condition);

बिल्कुल बराबर हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.