c++ - जबकि(1) बनाम। के लिए(;;) क्या कोई गति अंतर है?




perl optimization (13)

दीर्घ संस्करण...

पर्ल स्क्रिप्ट में while (1) उपयोग को देखने के बाद आज एक सहकर्मी ने जोर दिया कि for (;;) तेज है। मैंने तर्क दिया कि उन्हें वही उम्मीद होनी चाहिए कि दुभाषिया किसी भी मतभेद को अनुकूलित करेगा। मैंने एक ऐसी स्क्रिप्ट स्थापित की जो लूप पुनरावृत्तियों के लिए 1,000,000,000 चलाएगी और लूप के समान संख्या और बीच के समय को रिकॉर्ड करेगी। मुझे कोई सराहनीय अंतर नहीं मिला। मेरे सहकर्मी ने कहा कि एक प्रोफेसर ने उन्हें बताया था कि while (1) तुलना 1 == 1 कर रहा था और for (;;) नहीं था। हमने सी ++ के साथ 100x पुनरावृत्तियों की संख्या के साथ एक ही परीक्षण दोहराया और अंतर नगण्य था। हालांकि यह एक ग्राफिक उदाहरण था कि कितनी तेजी से संकलित कोड बनाम एक स्क्रिप्टिंग भाषा बनाम हो सकता है।

लघु संस्करण...

क्या आपको for (;;) while (1) पसंद करने का कोई कारण है यदि आपको एक अनंत लूप को तोड़ने की आवश्यकता है?

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

अद्यतन: उपर्युक्त सह-कार्यकर्ता ने नीचे प्रतिक्रिया के साथ वजन कम किया।

यदि इसे दफनाया जाता है तो यहां उद्धृत किया गया है।

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


Perl में, वे एक ही opcodes में परिणाम:

$ perl -MO=Concise -e 'for(;;) { print "foo\n" }'
a  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 2 -e:1) v ->3
9     <2> leaveloop vK/2 ->a
3        <{> enterloop(next->8 last->9 redo->4) v ->4
-        <@> lineseq vK ->9
4           <;> nextstate(main 1 -e:1) v ->5
7           <@> print vK ->8
5              <0> pushmark s ->6
6              <$> const[PV "foo\n"] s ->7
8           <0> unstack v ->4
-e syntax OK

$ perl -MO=Concise -e 'while(1) { print "foo\n" }'
a  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 2 -e:1) v ->3
9     <2> leaveloop vK/2 ->a
3        <{> enterloop(next->8 last->9 redo->4) v ->4
-        <@> lineseq vK ->9
4           <;> nextstate(main 1 -e:1) v ->5
7           <@> print vK ->8
5              <0> pushmark s ->6
6              <$> const[PV "foo\n"] s ->7
8           <0> unstack v ->4
-e syntax OK

इसी प्रकार जीसीसी में:

#include <stdio.h>

void t_while() {
    while(1)
        printf("foo\n");
}

void t_for() {
    for(;;)
        printf("foo\n");
}

    .file   "test.c"
    .section    .rodata
.LC0:
    .string "foo"
    .text
.globl t_while
    .type   t_while, @function
t_while:
.LFB2:
    pushq   %rbp
.LCFI0:
    movq    %rsp, %rbp
.LCFI1:
.L2:
    movl    $.LC0, %edi
    call    puts
    jmp .L2
.LFE2:
    .size   t_while, .-t_while
.globl t_for
    .type   t_for, @function
t_for:
.LFB3:
    pushq   %rbp
.LCFI2:
    movq    %rsp, %rbp
.LCFI3:
.L5:
    movl    $.LC0, %edi
    call    puts
    jmp .L5
.LFE3:
    .size   t_for, .-t_for
    .section    .eh_frame,"a",@progbits
.Lframe1:
    .long   .LECIE1-.LSCIE1
.LSCIE1:
    .long   0x0
    .byte   0x1
    .string "zR"
    .uleb128 0x1
    .sleb128 -8
    .byte   0x10
    .uleb128 0x1
    .byte   0x3
    .byte   0xc
    .uleb128 0x7
    .uleb128 0x8
    .byte   0x90
    .uleb128 0x1
    .align 8
.LECIE1:
.LSFDE1:
    .long   .LEFDE1-.LASFDE1
.LASFDE1:
    .long   .LASFDE1-.Lframe1
    .long   .LFB2
    .long   .LFE2-.LFB2
    .uleb128 0x0
    .byte   0x4
    .long   .LCFI0-.LFB2
    .byte   0xe
    .uleb128 0x10
    .byte   0x86
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI1-.LCFI0
    .byte   0xd
    .uleb128 0x6
    .align 8
.LEFDE1:
.LSFDE3:
    .long   .LEFDE3-.LASFDE3
.LASFDE3:
    .long   .LASFDE3-.Lframe1
    .long   .LFB3
    .long   .LFE3-.LFB3
    .uleb128 0x0
    .byte   0x4
    .long   .LCFI2-.LFB3
    .byte   0xe
    .uleb128 0x10
    .byte   0x86
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI3-.LCFI2
    .byte   0xd
    .uleb128 0x6
    .align 8
.LEFDE3:
    .ident  "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
    .section    .note.GNU-stack,"",@progbits

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


एक दूसरे को पसंद करने के लिए बहुत अधिक कारण नहीं है। मुझे लगता है कि while(1) और विशेष रूप से while(true) for(;;) अधिक पठनीय हैं, लेकिन यह मेरी प्राथमिकता है।


जीसीसी का उपयोग करके, वे दोनों एक ही असेंबली भाषा में संकलित लगते हैं:

L2:
        jmp     L2

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

while (1) {
     last if( condition1 );
     code();
     more_code(); 
     last if( condition2 ); 
     even_more_code(); 
}

वास्तव में किसी अन्य तरीके से प्रभावी ढंग से प्रतिनिधित्व नहीं किया जा सकता है। निकास चर बनाने के बिना और इसे समन्वयित रखने के लिए काले जादू कर नहीं।

यदि आपके पास अधिक गोटो-एस्क सिंटैक्स के लिए एक प्रवृत्ति है, तो स्कोप को सीमित करने वाली कुछ सायन का उपयोग करें।

flow: { 

   if ( condition ){ 
      redo flow;
   }
   if ( othercondition ){ 
       redo flow;
   }
   if ( earlyexit ){ 
       last flow;
   }
   something(); # doesn't execute when earlyexit is true 
}

अंततः गति महत्वपूर्ण नहीं है

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

आम तौर पर यह लूप और लूप का क्या तरीका है।

आपको पठनीयता और संक्षिप्तता के लिए "अनुकूलन" करना चाहिए, और अगले कोड को ढूंढने वाले अगले गरीब चूसने वाले को समस्या को समझाने के लिए जो कुछ भी सर्वोत्तम है उसे लिखें।

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

सिर्फ इसलिए कि आप स्पेगेटी कोड बना सकते हैं इसका मतलब यह नहीं है कि आपको चाहिए


मुझे आश्चर्य है कि किसी ने भी for (;;) बनाम while (1) for (;;) सही ढंग से परीक्षण नहीं किया है while (1) perl में!

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

सौभाग्य से perl एक सुविधाजनक बेंचमार्क मॉड्यूल है जिसे हम निम्नानुसार बेंचमार्क लागू करने के लिए उपयोग कर सकते हैं:

#!/usr/bin/perl -w

use Benchmark qw( cmpthese );

sub t_for   { eval 'die; for (;;) { }'; }
sub t_for2  { eval 'die; for (;;)  { }'; }
sub t_while { eval 'die; while (1) { }'; }

cmpthese(-60, { for => \&t_for, for2 => \&t_for2, while => \&t_while });

ध्यान दें कि मैं लूप के अनंत के दो अलग-अलग संस्करणों का परीक्षण कर रहा हूं: एक जो लूप की तुलना में छोटा है और दूसरा एक जिसमें अतिरिक्त लूप के समान लंबाई बनाने के लिए अतिरिक्त स्थान है।

उबंटू 11.04 x86_64 पर perl 5.10.1 के साथ मुझे निम्नलिखित परिणाम मिलते हैं:

          Rate   for  for2 while
for   100588/s    --   -0%   -2%
for2  100937/s    0%    --   -1%
while 102147/s    2%    1%    --

जबकि लूप स्पष्ट रूप से इस मंच पर विजेता है।

5.14.1 के साथ फ्रीबीएसडी 8.2 x86_64 पर:

         Rate   for  for2 while
for   53453/s    --   -0%   -2%
for2  53552/s    0%    --   -2%
while 54564/s    2%    2%    --

जबकि लूप भी विजेता है।

5.14.1 के साथ फ्रीबीएसडी 8.2 i386 पर:

         Rate while   for  for2
while 24311/s    --   -1%   -1%
for   24481/s    1%    --   -1%
for2  24637/s    1%    1%    --

आश्चर्यजनक रूप से एक अतिरिक्त जगह के साथ लूप के लिए सबसे तेज़ विकल्प है!

मेरा निष्कर्ष यह है कि प्रोग्रामर गति के लिए अनुकूलन कर रहा है, जबकि समय लूप x86_64 मंच पर इस्तेमाल किया जाना चाहिए। जाहिर है अंतरिक्ष के लिए अनुकूलन के दौरान एक लूप का उपयोग किया जाना चाहिए। मेरे परिणाम दुर्भाग्य से अन्य प्लेटफार्मों के संबंध में अनिश्चित हैं।


मुझे आश्चर्य है कि किसी ने भी वांछित असेंबली के अनुरूप, अधिक प्रत्यक्ष रूप की पेशकश नहीं की है:

forever:
     do stuff;
     goto forever;

मैंने इसके बारे में एक बार सुना।

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


यदि कंपाइलर कोई अनुकूलन नहीं करता है, तो for(;;) हमेशा while(true) से तेज़ होगा। ऐसा इसलिए है क्योंकि समय-समय पर कथन का मूल्यांकन होता है, लेकिन बयान के लिए बिना शर्त कूद है। लेकिन अगर कंपाइलर नियंत्रण प्रवाह को अनुकूलित करता है, तो यह कुछ ऑपकोड उत्पन्न कर सकता है। आप आसानी से disassembly कोड पढ़ सकते हैं।

पीएस आप इस तरह एक अनंत पाश लिख सकते हैं:

#define EVER ;;
  //...
  for (EVER) {
    //...
  }

विज़ुअल सी ++ कंपाइलर के लिए चेतावनी उत्सर्जित करने के लिए उपयोग किया जाता है

while (1) 

(निरंतर अभिव्यक्ति) लेकिन इसके लिए नहीं

for (;;)

मैंने उस कारण से for (;;) लिए पसंद करने का अभ्यास जारी रखा है, लेकिन मुझे नहीं पता कि संकलक अभी भी इन दिनों क्या करता है।


सिद्धांत रूप में, एक पूरी तरह से बेवकूफ संकलक बाइनरी (अंतरिक्ष को बर्बाद करने) में शाब्दिक '1' को स्टोर कर सकता है और यह देखने के लिए जांच सकता है कि 1 == 0 प्रत्येक पुनरावृत्ति (समय और अधिक जगह बर्बाद कर रहा है)।

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


for (;;) बनाम while (1) बहस के लिए संक्षेप में संक्षेप में यह स्पष्ट है कि पुराने पुराने ऑप्टिमाइज़िंग कंपाइलर्स के दिनों में पूर्व तेज था, यही कारण है कि आप इसे पुराने कोड बेस जैसे शेर यूनिक्स स्रोत कोड में देखते हैं कमेंटरी, हालांकि बैडस ऑप्टिमाइज़िंग कंपिलरों की उम्र में उन लाभों को युग्मित करने के लिए अनुकूलित किया गया है, इस तथ्य के साथ कि उत्तरार्द्ध पूर्व की तुलना में समझना आसान है, मुझे विश्वास है कि यह अधिक बेहतर होगा।


for(;;) इस पुराने कंपाइलर्स के साथ टर्बो सी के परिणामस्वरूप तेज़ कोड होता है while(1)

आज जीसीसी, विजुअल सी (मुझे लगता है कि लगभग सभी) कंपाइलर्स अच्छी तरह ऑप्टिमाइज़ करते हैं, और 4.7 मेगाहट्र्ज वाले सीपीयू का शायद ही कभी उपयोग किया जाता है।

उन दिनों में एक for( i=10; i; i-- ) लिए तेज़ था for( i=1; i <=10; i++ ) , क्योंकि तुलना 0 है, परिणामस्वरूप एक सीपीयू-शून्य-फ्लैग सशर्त जंप में परिणाम होता है। और शून्य-झंडा को अंतिम कमीशन ऑपरेशन ( i-- ) साथ संशोधित किया गया था, कोई अतिरिक्त सीएमपी-ऑपरेशन की आवश्यकता नहीं है।

    call    __printf_chk
    decl    %ebx          %ebx=iterator i 
    jnz     .L2
    movl    -4(%ebp), %ebx
    leave

और यहां अतिरिक्त cmpl के साथ for(i=1; i<=10; i++) साथ:

    call    __printf_chk
    incl    %ebx
    cmpl    $11, %ebx
    jne     .L2
    movl    -4(%ebp), %ebx
    leave

while(1) एक मुहावरे for(;;) जो अधिकांश कंपाइलरों द्वारा पहचाना जाता है।

मुझे यह देखकर खुशी हुई कि पर्ल until(0) भी पहचानता है।





performance