इतनी तेजी से कैसे एक कास्ट एक्सपर्ट का मूल्यांकन किया जा सकता है


13

मैं उन कॉन्स्ट्रेक्ट एक्सप्रेशन्स की कोशिश कर रहा हूं, जिनका संकलन समय पर किया गया है। लेकिन मैंने एक उदाहरण के साथ खेला जो संकलन के समय निष्पादित होने पर अविश्वसनीय रूप से तेज़ लगता है।

#include<iostream> 

constexpr long int fib(int n) { 
    return (n <= 1)? n : fib(n-1) + fib(n-2); 
} 

int main () {  
    long int res = fib(45); 
    std::cout << res; 
    return 0; 
} 

जब मैं इस कोड को चलाता हूं तो इसे चलाने में लगभग 7 सेकंड लगते हैं। अब तक सब ठीक है। लेकिन जब मैं बदलने long int res = fib(45)के लिए const long int res = fib(45)तो यह और भी एक दूसरे नहीं लेता है। मेरी समझ से इसका संकलन समय पर मूल्यांकन किया जाता है। लेकिन संकलन में लगभग 0.3 सेकंड लगते हैं

कंपाइलर इसका मूल्यांकन इतनी जल्दी कैसे कर सकता है, लेकिन रनटाइम में इसे इतना अधिक समय लगता है? मैं 5.4.0 gcc का उपयोग कर रहा हूँ।


7
मैं अनुमान लगाता हूं कि कंपाइलर फ़ंक्शन को कॉल करता है fib। आपके द्वारा ऊपर दी गई रिट्रेन्स संख्याओं का कार्यान्वयन धीमी गति से चलने वाला है। रनटाइम कोड में फ़ंक्शन मानों को कैशिंग करने का प्रयास करें और यह बहुत तेज़ होगा।
n314159

4
यह पुनरावर्ती रिट्रेसमेंट बहुत अक्षम है (इसका एक घातीय रनटाइम है), इसलिए मेरा अनुमान है कि संकलन समय का मूल्यांकन इससे अधिक चतुर है और गणना को अनुकूलित करता है।
ब्लेज़

1
@AlanBirtles हां मैंने इसे -O3 के साथ संकलित किया है।
पीटर 234

1
मुझे लगता है कि संकलक कैश फ़ंक्शन कॉल करता है फ़ंक्शन को केवल 2 ^ 45 बार के बजाय 46 बार (प्रत्येक संभावित तर्क 0-45 के लिए एक बार) evelitted करने की आवश्यकता है। हालाँकि मुझे नहीं पता कि अगर gcc उस तरह काम करता है।
चर्चिल

3
@Someprogrammerdude मुझे पता है। लेकिन मूल्यांकन इतनी जल्दी कैसे हो सकता है जब मूल्यांकन में इतना समय लगता है?
पीटर 234

जवाबों:


5

संकलक छोटे मानों को कैश करता है और रनटाइम संस्करण जितना करता है उतना पुन: उपयोग करने की आवश्यकता नहीं है।
(ऑप्टिमाइज़र बहुत अच्छा है और विशेष मामलों के साथ बहुत सारे कोड उत्पन्न करता है, विशेष मामलों के साथ जो मेरे लिए समझ से बाहर हैं; भोले 2 ^ 45 पुनरावर्ती घंटों लगेंगे।)

यदि आप पिछले मान भी संग्रहीत करते हैं:

int cache[100] = {1, 1};

long int fib(int n) {
    int res = cache[n];
    return res ? res : (cache[n] = fib(n-1) + fib(n-2));
} 

रनटाइम संस्करण संकलक की तुलना में बहुत तेज है।


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

... कैशिंग कंपाइलर के बजाय यह संभव कंपाइलर भी है जो फ़ाइबर (n-2) और फ़ाइबर (n-1) के बीच कुछ संबंध साबित करने में सक्षम है और फ़ाइब (n-1) को कॉल करने के बजाय फ़ाइबर (n-2) का उपयोग करता है ) गणना करने के लिए मूल्य। मुझे लगता है कि मैं वही देखता हूं जो मुझे 5.4-कॉन्स्ट्रीम हटाने और -ओ 2 के आउटपुट में दिखता है।
सुमा

1
क्या आपके पास एक लिंक या अन्य स्रोत है जो बताता है कि संकलन समय पर क्या अनुकूलन किया जा सकता है?
पीटर 234

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

@ सुमा यह केवल एक बार पुनरावृत्ति करने के लिए कोई समस्या नहीं है। चूंकि एक पुनरावृत्त संस्करण है, तो निश्चित रूप से एक पुनरावर्ती संस्करण भी है, जो उदाहरण पूंछ पुनरावृत्ति के लिए उपयोग करता है।
Ctx

1

आप 5.4 के साथ दिलचस्प पा सकते हैं फ़ंक्शन पूरी तरह से समाप्त नहीं हुआ है, आपको इसके लिए कम से कम 6.1 की आवश्यकता है।

मुझे नहीं लगता कि कोई कैशिंग हो रही है। मुझे विश्वास है कि आशावादी के बीच संबंध साबित करने के लिए पर्याप्त स्मार्ट है fib(n - 2)और fib(n-1)दूसरी कॉल को पूरी तरह से टाल देता है। यह जीसीसी 5.4 आउटपुट (गॉडबोल्ट से प्राप्त) नहीं -O2 के साथ है constexpr:

fib(long):
        cmp     rdi, 1
        push    r12
        mov     r12, rdi
        push    rbp
        push    rbx
        jle     .L4
        mov     rbx, rdi
        xor     ebp, ebp
.L3:
        lea     rdi, [rbx-1]
        sub     rbx, 2
        call    fib(long)
        add     rbp, rax
        cmp     rbx, 1
        jg      .L3
        and     r12d, 1
.L2:
        lea     rax, [r12+rbp]
        pop     rbx
        pop     rbp
        pop     r12
        ret
.L4:
        xor     ebp, ebp
        jmp     .L2

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


मुझे लगता है कि मैं गलत हूं। .L3 में एक लूप होता है, और सभी निचले तंतुओं पर लूपिंग होती है। -O2 के साथ यह अभी भी घातीय है।
सुमा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.