Bir sabit ifade nasıl bu kadar hızlı değerlendirilebilir?


13

Derleme zamanında değerlendirilen const ifadelerini deniyorum. Ancak derleme zamanında yürütüldüğünde inanılmaz hızlı görünen bir örnekle oynadım.

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

Bu kodu çalıştırdığımda çalıştırmak yaklaşık 7 saniye sürer. Çok uzak çok iyi. Değişmek Ama long int res = fib(45)için const long int res = fib(45)bunun bile bir saniye değil sürer. Anladığım kadarıyla derleme zamanında değerlendirilir. Ama derleme yaklaşık 0.3 saniye sürüyor

Derleyici bunu nasıl bu kadar hızlı değerlendirebilir, ancak çalışma zamanında çok daha fazla zaman alır? Gcc 5.4.0 kullanıyorum.


7
Derleyicinin işlevin çağırdığı önbelleğe aldığını tahmin ediyorum fib. Yukarıda bulunan fibonacci sayılarının uygulanması yavaştır. Çalışma zamanı kodundaki işlev değerlerini önbelleğe almayı deneyin; çok daha hızlı olur.
n314159

4
Bu özyinelemeli fibonacci çok verimsizdir (üstel bir çalışma süresine sahiptir), bu yüzden tahminim derleme zamanı değerlendirmesinin bundan daha akıllı olduğu ve hesaplamayı optimize ettiği.
Blaze

1
@AlanBirtles Evet -O3 ile derledim.
Peter234

1
Derleyici önbellek işlev çağrıları işlev 2 ^ 45 kez yerine yalnızca 46 kez (her olası argüman için 0-45) bir kez değerlendirilmesi gerektiğini varsayıyoruz. Ancak gcc'nin böyle çalışıp çalışmadığını bilmiyorum.
churill

3
@Akmarogrammerdude biliyorum. Ancak değerlendirme çalışma zamanında çok zaman aldığında derleme nasıl bu kadar hızlı olabilir?
Peter234

Yanıtlar:


5

Derleyici daha küçük değerleri önbelleğe alır ve çalışma zamanı sürümü kadar yeniden hesaplaması gerekmez.
(Optimize edici çok iyi ve benim için anlaşılmaz olan özel durumlarla hile de dahil olmak üzere çok sayıda kod üretir; saf 2 ^ 45 özyinelemeleri saatler sürecektir.)

Önceki değerleri de kaydederseniz:

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

çalışma zamanı sürümü derleyiciden çok daha hızlıdır.


Biraz önbellekleme yapmadığınız sürece iki kez tekrar etmekten kaçınmanın bir yolu yoktur. Doktorun bazı önbellekleme uyguladığını düşünüyor musunuz? Bunu gerçekten ilginç olacağı için derleyici çıktısında gösterebiliyor musunuz?
Suma

... önbellek yerine derleyici de fib (n-2) ve fib (n-1) arasında bir ilişki olduğunu kanıtlayabilir ve fib (n-2) yerine fib (n-2) kullanmak yerine ) değerini hesaplayın. Ben constexpr kaldırmak ve -O2 kullanarak whne 5.4 çıkışında gördüğüm ile eşleşen düşünüyorum.
Suma

1
Derleme zamanında hangi optimizasyonların yapılabileceğini açıklayan bir bağlantınız veya başka bir kaynağınız var mı?
Peter234

Gözlenebilir davranış değişmediği sürece, optimize edici neredeyse her şeyi yapmakta serbesttir. Verilen fibfonksiyonun hiçbir yan etkisi yoktur (referanslar harici değişkenler yoktur, çıkış sadece girdilere bağlıdır), akıllı bir optimize edici ile çok şey yapılabilir.
Suma

@Suma Sadece bir kez tekrarlamak sorun değil. Yinelemeli bir sürüm olduğu için, elbette, örneğin kuyruk özyinelemesini kullanan özyinelemeli bir sürüm de vardır.
Ctx

1

5.4 ile ilginç bulabilirsiniz fonksiyon tamamen ortadan kaldırılmaz, bunun için en az 6.1 gerekir.

Herhangi bir önbellekleme olduğunu sanmıyorum. Ben iyileştirici arasındaki ilişkiyi kanıtlamak için akıllı yeterlidir eminim fib(n - 2)ve fib(n-1)tamamen ikinci çağrıyı önler. Bu constexprve -O2 içermeyen GCC 5.4 çıkışı (godbolt'tan elde edilir) :

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 ile çıktı anlamıyorum itiraf etmeliyim - üretilen kod şaşırtıcı derecede karmaşık, çok sayıda bellek erişim ve işaretçi aritmetik ile ve bu ayarlarla yapılan bazı önbellekleme (not) oldukça mümkündür.


Sanırım yanılıyorum. .L3'te bir ilmek vardır ve fib tüm alt fiberin üzerinde ilmek yapar. -O2 ile hala üsteldir.
Suma
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.