Varsa, hangi C ++ derleyicileri kuyruk özyineleme optimizasyonu yapar?


150

Bana öyle geliyor ki hem C hem de C ++ 'da kuyruk özyineleme optimizasyonu yapmak çok iyi çalışır, ancak hata ayıklama sırasında asla bu optimizasyonu gösteren bir çerçeve yığını görmüyorum. Bu iyi bir şey, çünkü yığın bana özyinenin ne kadar derin olduğunu söylüyor. Ancak, optimizasyon da hoş olurdu.

Bu optimizasyonu herhangi bir C ++ derleyicisi yapıyor mu? Neden? Neden olmasın?

Derleyiciye bunu yapmasını nasıl söyleyebilirim?

  • MSVC için: /O2veya/Ox
  • GCC için: -O2veya-O3

Derleyicinin bunu belirli bir durumda yapıp yapmadığını kontrol etmeye ne dersiniz?

  • MSVC için, PDB çıkışının kodu izleyebilmesini sağlayın, ardından kodu inceleyin
  • GCC için ..?

Derleyici tarafından belirli bir işlevin bu şekilde optimize edilip edilmediğini nasıl belirleyeceğimi hala öneriyorum (Konrad'ın bunu üstlenmemi söylediğine dair güven verici bulsam da)

Derleyicinin bunu sonsuz bir özyineleme yapıp sonsuz bir döngü veya yığın taşmasıyla sonuçlayıp sağlamadığını kontrol etmek her zaman mümkündür (bunu GCC ile yaptım ve -O2yeterli olduğunu öğrendim ), ama olmak istiyorum Sonlandırılacağını bildiğim belirli bir işlevi kontrol edebilir. Bunu kontrol etmenin kolay bir yoluna sahip olmak isterim :)


Bazı testlerden sonra, yıkıcıların bu optimizasyonu yapma olasılığını mahvettiğini keşfettim. Dönüş ifadesi başlamadan önce kapsam dışına çıktıklarından emin olmak için bazen belirli değişkenlerin ve geçici programların kapsamını değiştirmek faydalı olabilir.

Kuyruk çağrısından sonra herhangi bir yıkıcı çalıştırılması gerekiyorsa, kuyruk çağrısı optimizasyonu yapılamaz.

Yanıtlar:


129

Mevcut tüm ana akım derleyiciler , aşağıdakiler gibi karşılıklı olarak yinelenen çağrılar için bile kuyruk çağrı optimizasyonunu oldukça iyi (ve on yıldan fazla bir süredir yapmışlardır) gerçekleştirir :

int bar(int, int);

int foo(int n, int acc) {
    return (n == 0) ? acc : bar(n - 1, acc + 2);
}

int bar(int n, int acc) {
    return (n == 0) ? acc : foo(n - 1, acc + 1);
}

Derleyicinin optimizasyonu yapmasına izin vermek basittir: Hız için optimizasyonu açın:

  • MSVC için /O2veya kullanın /Ox.
  • GCC, Clang ve ICC için şunu kullanın: -O3

Derleyicinin optimizasyon yapıp yapmadığını kontrol etmenin kolay bir yolu, aksi takdirde yığın taşmasına veya montaj çıktısına bakılmasına neden olacak bir çağrı yapmaktır.

İlginç bir tarihsel not olarak, Mark Probst tarafından yapılan bir diploma tezi sırasında C için kuyruk çağrı optimizasyonu GCC'ye eklendi . Tez, uygulamadaki bazı ilginç uyarıları açıklamaktadır. Okumaya değer.


ICC inanırdı. Bildiğim kadarıyla ICC piyasadaki en hızlı kodu üretir.
Paul Nathan

35
@Paul Soru, ICC kodunun hızının ne kadarına kuyruk çağrı optimizasyonları gibi algoritmik optimizasyonların neden olduğu ve ne kadarının yalnızca Intel'in kendi işlemcileri hakkındaki samimi bilgileriyle yapabileceği önbellek ve mikroyapı optimizasyonlarından kaynaklandığıdır.
Imagist

6
gcc-foptimize-sibling-calls"kardeş ve kuyruk yinelemeli çağrıları optimize etmek" için daha dar bir seçeneğe sahiptir . (E göre bu seçenek gcc(1)sürümleri 4.4, 4.7 ve 4.8, çeşitli platformlar hedeflenmesi için kılavuz sayfaları) seviyelerinde etkindir -O2, -O3, -Os.
FooF

Ayrıca, açıkça optimizasyon talep etmeden DEBUG modunda çalışmak HİÇBİR optimizasyon YAPMAYACAKTIR. Gerçek sürüm modu EXE için PDB'yi etkinleştirebilir ve bunun üzerinden adım atmayı deneyebilirsiniz, ancak Sürüm modunda hata ayıklamanın komplikasyonları olduğunu unutmayın - görünmez / soyulmuş değişkenler, birleştirilmiş değişkenler, bilinmeyen / beklenmeyen kapsamda değişkenlerin kapsamı dışında kalan değişkenler, asla girmeyecek değişkenler kapsamı ve yığın düzeyinde adresler ve - iyi - Birleştirilmiş veya eksik yığın çerçeveleri ile gerçek sabitler haline geldi. Genellikle birleştirilmiş yığın çerçeveleri, callee'nin satır içi olduğu anlamına gelir ve eksik / geri bağlanmış çerçeveler muhtemelen kuyruk çağrısı anlamına gelir.
Петър Петров

21

gcc 4.3.2 tamamen bu işlevi (boktan / önemsiz atoi()uygulama) içine sıralar main(). Optimizasyon seviyesi -O1. Ben bile onu değiştirerek (onunla oynayın eğer fark staticetmek externben programın doğruluğundan buna bağlıdır olmaz bu yüzden, kuyruk özyineleme, oldukça hızlı kaybolur.

#include <stdio.h>
static int atoi(const char *str, int n)
{
    if (str == 0 || *str == 0)
        return n;
    return atoi(str+1, n*10 + *str-'0');
}
int main(int argc, char **argv)
{
    for (int i = 1; i != argc; ++i)
        printf("%s -> %d\n", argv[i], atoi(argv[i], 0));
    return 0;
}

1
Yine de bağlantı zamanı optimizasyonu etkinleştirebilirsiniz ve bir externyöntem bile inline olabilir sanırım .
Konrad Rudolph

5
Garip. Sadece GCC 4.2.3 (x86, Slackware 12.1) ve gcc 4.6.2 (hırıltılı AMD64, Debian) ve test ile-O1 orada hiçbir inlining ve hiçbir kuyruk özyineleme optimizasyonu . Sen zorunda kullanmak -O2bunun için (şimdi oldukça eski olan 4.2.x, içinde, iyi, hala inlined edilmeyecektir). BTW Ayrıca gcc'nin kesinlikle bir kuyruk olmasa bile özyinelemeyi optimize edebileceğini de eklemeye değer (akümülatörsüz faktöriyel gibi).
przemoc

16

Açıkça olduğu gibi (derleyiciler siz istemedikçe bu tür bir optimizasyon yapmazlar), C ++: destructors'ta kuyruk çağrı optimizasyonu konusunda bir karmaşıklık vardır.

Gibi bir şey verildi:

   int fn(int j, int i)
   {
      if (i <= 0) return j;
      Funky cls(j,i);
      return fn(j, i-1);
   }

Derleyici (genel olarak) kuyruk çağrısı bunu optimize edemez, çünkü özyinelemeli çağrı döndükten cls sonra yıkıcıyı çağırması gerekir.

Bazen derleyici, yıkıcının harici olarak görünür hiçbir yan etkisi olmadığını görebilir (bu nedenle erken yapılabilir), ancak çoğu zaman yapamaz.

Bunun özellikle yaygın bir şekli Funky, aslında bir std::vectorveya benzer olduğu yerdir .


Benim için çalışmıyor. Sistemler, cevap düzenleninceye kadar oylamın kilitli olduğunu söylüyor.
hmuelner

Sadece cevabı düzenledim (parantezleri kaldırdım) ve şimdi aşağı oyumu geri alabilirim.
hmuelner

11

Çoğu derleyici bir hata ayıklama derlemesinde herhangi bir optimizasyon yapmaz.

VC kullanıyorsanız, PDB bilgileri açıkken bir sürüm oluşturmayı deneyin - bu, optimize edilmiş uygulamayı izlemenizi sağlar ve umarım ne istediğinizi görmelisiniz. Bununla birlikte, optimize edilmiş bir yapının hata ayıklama ve izlemenin sizi her yere atlayacağını ve çoğu zaman değişkenleri doğrudan kayıtlara girdiği veya tamamen optimize edildiği için doğrudan denetleyemeyeceğinizi unutmayın. "İlginç" bir deneyim ...


2
gcc neden -g -O3'ü deneyin ve bir hata ayıklama derlemesinde optimizasyon elde edin. xlC aynı davranışa sahiptir.
g24l

"Çoğu derleyici" dediğinizde: hangi derleyici koleksiyonlarını düşünüyorsunuz? Belirtildiği gibi, hata ayıklama oluşturma sırasında optimizasyon gerçekleştiren en az iki derleyici vardır - ve bildiğim kadarıyla VC bunu da yapar (belki de değiştir ve devam et seçeneğini etkinleştiriyorsanız).
23'te skyking

7

Greg'in belirttiği gibi, derleyiciler hata ayıklama modunda yapmazlar. Hata ayıklama yapılarının bir eşya derlemesinden daha yavaş olması iyidir, ancak daha sık çökmemelidir: ve bir kuyruk çağrısı optimizasyonuna bağlıysanız, tam olarak bunu yapabilirler. Bu nedenle, kuyruk çağrısını normal bir döngü olarak yeniden yazmak genellikle en iyisidir. :-(

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.