const expr สามารถประเมินได้อย่างไรอย่างรวดเร็ว


13

ฉันได้ทดลองใช้นิพจน์ const ซึ่งประเมินเวลารวบรวม แต่ฉันเล่นกับตัวอย่างที่ดูเหมือนรวดเร็วอย่างไม่น่าเชื่อเมื่อดำเนินการในเวลารวบรวม

#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 วินาที

คอมไพเลอร์สามารถประเมินสิ่งนี้ได้อย่างรวดเร็วอย่างไร แต่ ณ รันไทม์มันต้องใช้เวลามากขึ้น? ฉันใช้ gcc 5.4.0


7
ฉันคาดเดาว่าคอมไพเลอร์แคชฟังก์ชันที่เรียกfibใช้ การใช้ตัวเลขฟีโบนักชีที่คุณมีอยู่ด้านบนนั้นช้ามาก ลองแคชค่าฟังก์ชันในโค้ดรันไทม์และมันจะเร็วขึ้นมาก
n314159

4
fibonacci แบบเรียกซ้ำนี้ไม่มีประสิทธิภาพมาก (มีรันไทม์แบบเอ็กซ์โปเนนเชียล) ดังนั้นฉันเดาว่าการประเมินเวลาคอมไพล์นั้นฉลาดกว่านี้มากและปรับการคำนวณให้เหมาะสมที่สุด
Blaze

1
@ AlanBirtles ใช่ฉันรวบรวมมันด้วย -O3
Peter234

1
ฉันคิดว่าคอมไพเลอร์แคชฟังก์ชั่นการโทรฟังก์ชั่นจะต้อง eveluated เพียง 46 ครั้ง (ครั้งเดียวสำหรับแต่ละอาร์กิวเมนต์ที่เป็นไปได้ 0-45) แทน 2 ^ 45 ครั้ง อย่างไรก็ตามฉันไม่รู้ว่า gcc ทำงานเช่นนั้นหรือไม่
churill

3
@ โปรแกรมบางโปรแกรมฉันรู้ แต่การรวบรวมสามารถทำได้อย่างรวดเร็วเพียงใดเมื่อการประเมินใช้เวลานานในการรันไทม์?
Peter234

คำตอบ:


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));
} 

เวอร์ชั่นรันไทม์นั้นเร็วกว่าคอมไพเลอร์มาก


ไม่มีวิธีหลีกเลี่ยงการเรียกซ้ำสองครั้งเว้นแต่ว่าคุณจะทำการแคช คุณคิดว่าเครื่องมือเพิ่มประสิทธิภาพใช้แคชบ้างไหม? คุณสามารถแสดงสิ่งนี้ในคอมไพเลอร์เอาท์พุทเพราะมันน่าสนใจจริง ๆ ?
Suma

... มันเป็นไปได้ที่คอมไพเลอร์แทนที่จะเป็นแคชคอมไพเลอร์สามารถพิสูจน์ความสัมพันธ์ระหว่าง fib (n-2) และ fib (n-1) และแทนการเรียก fib (n-1) จะใช้กับ fib (n-2) ) มูลค่าที่จะคำนวณ ฉันคิดว่าตรงกับสิ่งที่ฉันเห็นในผลลัพธ์ของ 5.4 เมื่อเอา ​​constexpr และใช้ -O2
Suma

1
คุณมีลิงค์หรือแหล่งข้อมูลอื่นที่อธิบายการเพิ่มประสิทธิภาพที่สามารถทำได้ในเวลารวบรวมหรือไม่?
Peter234

ตราบใดที่พฤติกรรมที่สังเกตได้ไม่เปลี่ยนแปลงเครื่องมือเพิ่มประสิทธิภาพสามารถทำอะไรได้เกือบทุกอย่าง fibฟังก์ชั่นที่กำหนดไม่มีผลข้างเคียง (อ้างอิงไม่มีตัวแปรภายนอกเอาต์พุตขึ้นอยู่กับอินพุตเท่านั้น) ด้วยเครื่องมือเพิ่มประสิทธิภาพที่ชาญฉลาดสามารถทำได้หลายอย่าง
Suma

@Suma ไม่มีปัญหาในการเรียกคืนเพียงครั้งเดียว เนื่องจากมีรุ่นซ้ำแล้วซ้ำอีกแน่นอนยังมีรุ่นเรียกซ้ำที่ใช้สำหรับการเรียกซ้ำหางเช่นกัน
Ctx

1

คุณอาจพบว่ามีความน่าสนใจกับ 5.4 ฟังก์ชั่นที่ไม่ได้ตัดออกอย่างสมบูรณ์คุณต้องมีอย่างน้อย 6.1 สำหรับสิ่งนั้น

ฉันไม่คิดว่าจะมีการแคชเกิดขึ้น ฉันเชื่อว่าเครื่องมือเพิ่มประสิทธิภาพนั้นฉลาดพอที่จะพิสูจน์ความสัมพันธ์ระหว่างfib(n - 2)และfib(n-1)และหลีกเลี่ยงการโทรสายที่สองได้อย่างสมบูรณ์ นี่คือเอาต์พุต GCC 5.4 (ได้มาจาก godbolt) โดยไม่มีconstexprและ -O2:

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 มันยังคงเป็นเลขชี้กำลัง
Suma
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.