2014-07-18 6 views
74

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

परिणाम हैं:

[email protected]:~/tmp/js$ time node bla.js    # * JavaScript with node * 
0 

real 0m1.222s 
user 0m1.190s 
sys 0m0.015s 
[email protected]:~/tmp/js$ time ruby foo.rb    # * Ruby * 
0 

real 0m52.428s 
user 0m52.395s 
sys 0m0.028s 
[email protected]:~/tmp/js$ time python blub.py   # * Python with CPython * 
0 

real 1m16.480s 
user 1m16.371s 
sys 0m0.080s 

[email protected]:~/tmp/js$ time pypy blub.py    # * Python with PyPy * 
0 

real 0m4.707s 
user 0m4.579s 
sys 0m0.028s 

[email protected]:~/tmp/js$ time ./cpp_non_optimized 1000 1000000 # * C++ with -O0 (gcc) * 
0 

real 0m1.702s 
user 0m1.699s 
sys 0m0.002s 
[email protected]:~/tmp/js$ time ./cpp_optimized 1000 1000000  # * C++ with -O3 (gcc) * 
0 

real 0m0.003s # (!!!) <---------------------------------- WHY? 
user 0m0.002s 
sys 0m0.002s 

मैं क्यों अनुकूलित सी ++ कोड परिमाण के तीन से अधिक आदेश तेजी से सब कुछ की तुलना में है अगर किसी को भी एक विवरण प्रदान कर सकते हैं सोच रहा हूँ।

सी ++ बेंचमार्क संकलन समय पर परिणाम पूर्व-कंप्यूटिंग को रोकने के लिए कमांड लाइन पैरामीटर का उपयोग करता है।

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

जावास्क्रिप्ट:

var s = 0; 
var outer = 1000; 
var inner = 1000000; 

for (var i = 0; i < outer; ++i) { 
    for (var j = 0; j < inner; ++j) { 
     ++s; 
    } 
    s -= inner; 
} 
console.log(s); 

पायथन:

s = 0 
outer = 1000 
inner = 1000000 

for _ in xrange(outer): 
    for _ in xrange(inner): 
     s += 1 
    s -= inner 
print s 

रूबी:

s = 0 
outer = 1000 
inner = 1000000 

outer_end = outer - 1 
inner_end = inner - 1 

for i in 0..outer_end 
    for j in 0..inner_end 
    s = s + 1 
    end 
    s = s - inner 
end 
puts s 

सी ++ :

#include <iostream> 
#include <cstdlib> 
#include <cstdint> 

int main(int argc, char* argv[]) { 
    uint32_t s = 0; 
    uint32_t outer = atoi(argv[1]); 
    uint32_t inner = atoi(argv[2]); 
    for (uint32_t i = 0; i < outer; ++i) { 
    for (uint32_t j = 0; j < inner; ++j) 
     ++s; 
    s -= inner; 
    } 
    std::cout << s << std::endl; 
    return 0; 
} 

सभा (जब जीसीसी एस -O3 -std = C++ 0x से ऊपर सी ++ कोड संकलन):

.file "bar.cpp" 
    .section .text.startup,"ax",@progbits 
    .p2align 4,,15 
    .globl main 
    .type main, @function 
main: 
.LFB1266: 
    .cfi_startproc 
    pushq %r12 
    .cfi_def_cfa_offset 16 
    .cfi_offset 12, -16 
    movl $10, %edx 
    movq %rsi, %r12 
    pushq %rbp 
    .cfi_def_cfa_offset 24 
    .cfi_offset 6, -24 
    pushq %rbx 
    .cfi_def_cfa_offset 32 
    .cfi_offset 3, -32 
    movq 8(%rsi), %rdi 
    xorl %esi, %esi 
    call strtol 
    movq 16(%r12), %rdi 
    movq %rax, %rbp 
    xorl %esi, %esi 
    movl $10, %edx 
    call strtol 
    testl %ebp, %ebp 
    je .L6 
    movl %ebp, %ebx 
    xorl %eax, %eax 
    xorl %edx, %edx 
    .p2align 4,,10 
    .p2align 3 
.L3:        # <--- Here is the loop 
    addl $1, %eax    # <--- 
    cmpl %eax, %ebx   # <--- 
    ja .L3      # <--- 
.L2: 
    movl %edx, %esi 
    movl $_ZSt4cout, %edi 
    call _ZNSo9_M_insertImEERSoT_ 
    movq %rax, %rdi 
    call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ 
    popq %rbx 
    .cfi_remember_state 
    .cfi_def_cfa_offset 24 
    popq %rbp 
    .cfi_def_cfa_offset 16 
    xorl %eax, %eax 
    popq %r12 
    .cfi_def_cfa_offset 8 
    ret 
.L6: 
    .cfi_restore_state 
    xorl %edx, %edx 
    jmp .L2 
    .cfi_endproc 
.LFE1266: 
    .size main, .-main 
    .p2align 4,,15 
    .type _GLOBAL__sub_I_main, @function 
_GLOBAL__sub_I_main: 
.LFB1420: 
    .cfi_startproc 
    subq $8, %rsp 
    .cfi_def_cfa_offset 16 
    movl $_ZStL8__ioinit, %edi 
    call _ZNSt8ios_base4InitC1Ev 
    movl $__dso_handle, %edx 
    movl $_ZStL8__ioinit, %esi 
    movl $_ZNSt8ios_base4InitD1Ev, %edi 
    addq $8, %rsp 
    .cfi_def_cfa_offset 8 
    jmp __cxa_atexit 
    .cfi_endproc 
.LFE1420: 
    .size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main 
    .section .init_array,"aw" 
    .align 8 
    .quad _GLOBAL__sub_I_main 
    .local _ZStL8__ioinit 
    .comm _ZStL8__ioinit,1,1 
    .hidden __dso_handle 
    .ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2" 
    .section .note.GNU-stack,"",@progbits 
+35

ठीक है मुझे लगता है कि यह एक व्याख्या की गई भाषा का उपर है। यह शुरू करने के लिए लगने वाले समय से बचने और दुभाषिया को बंद करने के लिए आपकी स्क्रिप्ट के भीतर से समय के लायक हो सकता है। –

+7

"व्याख्या की गई भाषा के उपरि" में जोड़ें तथ्य यह है कि कई व्याख्या की गई भाषाओं में एक जेआईटी कंपाइलर भी है जो कोड को चलाने के रूप में अनुकूलित करता है; आप कोड के कुछ अलग-अलग सेट्स को बेंचमार्क करना चाहते हैं, जो लंबे समय तक चलने की गारंटी रखते हैं (बड़ी संख्या में फैक्टरिंग? एनएच प्राइम ढूँढना?) यह देखने के लिए कि तुलना कैसे हिलाती है ... – abiessu

+4

[यह] (http://goo.gl/केएफओएल 6 ए) क्लीनर दिखता है। – edmz

उत्तर

100

अनुकूलक ने यह काम किया है कि बाद की रेखा के साथ आंतरिक लूप एक नो-ऑप है, और इसे समाप्त कर दिया गया है। दुर्भाग्य से यह बाहरी पाश को भी खत्म करने में कामयाब नहीं रहा है।

ध्यान दें कि node.js उदाहरण unoptimized C++ उदाहरण से तेज़ है, यह दर्शाता है कि V8 (नोड का जेआईटी कंपाइलर) कम से कम एक लूप को खत्म करने में कामयाब रहा है। हालांकि, इसके ऑप्टिमाइज़ेशन में कुछ ओवरहेड है, जैसे कि (किसी भी जेआईटी कंपाइलर की तरह) इसे ऑप्टिमाइज़ेशन और प्रोफाइल-निर्देशित पुन: अनुकूलन के अवसरों को संतुलित करने के अवसरों को संतुलित करना होगा।

+2

धन्यवाद, यह समाधान प्रतीत होता है।अनुकूलित निष्पादन योग्य निष्पादन करते समय, मैं पहली तर्क ('बाहरी' काउंटर) को बढ़ाते समय रनटाइम बढ़ाता हूं, लेकिन नहीं, अगर मैं दूसरे के साथ ऐसा करता हूं। –

21

मैं का एक पूरा विश्लेषण नहीं किया असेंबली, लेकिन ऐसा लगता है कि यह आंतरिक लूप के लूप को अनलॉक कर रहा था और यह पता चला कि आंतरिक के घटाव के साथ यह एक एनओपी है।

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

6

इसमें यह अनुकूलन कर, या यह कोड हर बार कार्यक्रम चलाया जाता है फिर से अनुकूलन करने के लिए है के बाद JIT संकलित कोड कैश करने के लिए कोई तरीका है?

अगर मैं अजगर में लिख रहे थे कि मैं क्या कर रहा था कोड का एक "भूमि के ऊपर" दृश्य प्राप्त करने के आकार में नीचे कोड कम करने की कोशिश होगी। की तरह (IMO पढ़ने के लिए बहुत आसान है) इस लिखने का प्रयास करें:

for i in range(outer): 
    innerS = sum(1 for _ in xrange(inner)) 
    s += innerS 
    s -= innerS 

या यहाँ तक कि s = sum(inner - inner for _ in xrange(outer))

2
for (uint32_t i = 0; i < outer; ++i) { 
    for (uint32_t j = 0; j < inner; ++j) 
     ++s; 
    s -= inner; 
} 

भीतरी पाश के बराबर है "एस + = भीतरी; j = भीतरी," जो एक अच्छा अनुकूलन कंपाइलर कर सकते हैं। के बाद से चर जे पाश के बाद चला गया है, पूरे कोड फिर

for (uint32_t i = 0; i < outer; ++i) { 
    s += inner; 
    s -= inner; 
} 

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

अब यह तय करने के लिए आप कितनी बार अनुकूलन करते हैं, और यह वास्तविक जीवन लाभ है या नहीं।

2

भले ही लूपों में बहुत सारे पुनरावृत्तियों हैं, फिर भी प्रोग्राम अब भी लंबे समय तक दुभाषिया/जेवीएम/शैल/आदि के स्टार्टअप समय के ऊपरी भाग से बचने के लिए पर्याप्त नहीं चल रहे हैं। कुछ वातावरण में ये बड़े पैमाने पर भिन्न हो सकते हैं - कुछ मामलों में * खांसी * जावा * खांसी * आपके वास्तविक कोड के पास कहीं भी हो जाने से पहले कई सेकंड लेती है।

आदर्श रूप से आप कोड के प्रत्येक टुकड़े के भीतर निष्पादन का समय लेंगे। यह सभी भाषाओं में सटीक रूप से ऐसा करना मुश्किल हो सकता है, लेकिन time का उपयोग करने से पहले और बाद में टिकों में घड़ी का समय प्रिंट करना भी बेहतर होगा, और यह काम करेगा क्योंकि आप शायद सुपर-सटीक समय से संबंधित नहीं हैं।

(मुझे लगता है कि यह वास्तव में संबंधित नहीं है कि सी ++ उदाहरण इतना तेज़ क्यों है - लेकिन यह अन्य परिणामों में कुछ बदलावशीलता को समझा सकता है। :))।

संबंधित मुद्दे