Çok tempüle bir C ++ kütüphanesi olan STAPL projesinde çalışıyorum. Arada sırada, derleme süresini azaltmak için tüm teknikleri tekrar gözden geçirmeliyiz. Burada kullandığımız teknikleri özetledim. Bu tekniklerin bazıları yukarıda listelenmiştir:
En çok zaman alan bölümleri bulma
Sembol uzunlukları ve derleme süresi arasında kanıtlanmış bir korelasyon olmamasına rağmen, daha küçük ortalama sembol boyutlarının tüm derleyicilerde derleme süresini iyileştirebileceğini gözlemledik. Yani ilk hedefleriniz kodunuzdaki en büyük sembolleri bulmaktır.
Yöntem 1 - Sembolleri boyuta göre sırala
nm
Komutları, sembolleri boyutlarına göre listelemek için kullanabilirsiniz :
nm --print-size --size-sort --radix=d YOUR_BINARY
Bu komutta --radix=d
boyutları ondalık sayılarla görmenizi sağlar (varsayılan hex'dir). Şimdi en büyük sembole bakarak, karşılık gelen sınıfı kırabileceğinizi belirleyin ve bir temel sınıftaki şablonlanmamış parçaları çarpanlarına ayırarak veya sınıfı birden çok sınıfa bölerek yeniden tasarlamaya çalışın.
Yöntem 2 - Sembolleri uzunluğa göre sıralama
Normal nm
komutu çalıştırabilir ve sembolleri uzunluklarına göre sıralamak için sık kullandığınız komut dosyasına ( AWK , Python vb.) Ekleyebilirsiniz . Deneyimlerimize dayanarak, bu yöntem adayları yöntem 1'den daha iyi hale getirmede en büyük sorunu tanımlar.
Yöntem 3 - Templight'ı kullanma
" Templight , şablon örneklemelerinin zamanını ve bellek tüketimini belirlemek ve şablon örnekleme işlemine introspeksiyon kazanmak için etkileşimli hata ayıklama oturumları gerçekleştirmek için Clang tabanlı bir araçtır".
Templight'ı LLVM ve Clang'a ( talimatlar ) bakarak ve Templight yamasını uygulayarak yükleyebilirsiniz. LLVM ve Clang için varsayılan ayar hata ayıklama ve iddialardadır ve bunlar derleme sürenizi önemli ölçüde etkileyebilir. Templight'ın her ikisine de ihtiyacı var gibi görünüyor, bu yüzden varsayılan ayarları kullanmanız gerekiyor. LLVM ve Clang yükleme işlemi yaklaşık bir saat kadar sürmelidir.
Düzeltme ekini uyguladıktan sonra templight++
, kodunuzu derlemek için yükleme sırasında belirttiğiniz oluşturma klasöründe kullanabilirsiniz.
Bunun PATH'nizde olduğundan emin olun templight++
. Şimdi derlemek CXXFLAGS
için Makefile'nize veya komut satırı seçeneklerinize aşağıdaki anahtarları ekleyin :
CXXFLAGS+=-Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system
Veya
templight++ -Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system
Derleme tamamlandıktan sonra, aynı klasörde bir .trace.memory.pbf ve .trace.pbf dosyası oluşturulur. Bu izleri görselleştirmek için bunları diğer biçimlere dönüştürebilen Templight Araçlarını kullanabilirsiniz . Templight-convert'i yüklemek için bu talimatları izleyin . Genellikle callgrind çıktısını kullanırız. Projeniz küçükse GraphViz çıktısını da kullanabilirsiniz:
$ templight-convert --format callgrind YOUR_BINARY --output YOUR_BINARY.trace
$ templight-convert --format graphviz YOUR_BINARY --output YOUR_BINARY.dot
Oluşturulan callgrind dosyası kullanılarak açılabilir , en çok zaman / bellek tüketen örneği izleyebileceğiniz kcachegrind .
Şablon örneklerinin sayısını azaltma
Şablon örneklemelerinin sayısını azaltmak için kesin bir çözüm olmamasına rağmen, yardımcı olabilecek birkaç kural vardır:
Birden fazla şablon bağımsız değişkenine sahip refactor sınıfları
Örneğin, bir sınıfınız varsa,
template <typename T, typename U>
struct foo { };
ve her ikisi de T
ve U
10 farklı seçenek olabilir, bu soyut farklı bir sınıfa kod ortak parçasıdır gidermek için 100. Bir şekilde bu sınıfın olası şablon instantiations artmıştır. Diğer yöntem miras inversiyonunu kullanmaktır (sınıf hiyerarşisini tersine çevirmek), ancak bu tekniği kullanmadan önce tasarım hedeflerinizin tehlikeye atılmadığından emin olun.
Tek tek çeviri birimlerine yeniden şablonlanmamış kod
Bu tekniği kullanarak ortak bölümü bir kez derleyebilir ve daha sonra diğer TU'larınızla (çeviri birimleri) bağlayabilirsiniz.
Harici şablon örneklerini kullan (C ++ 11'den beri)
Bir sınıfın tüm olası örneklerini biliyorsanız, bu tekniği farklı bir çeviri birimindeki tüm vakaları derlemek için kullanabilirsiniz.
Örneğin, içinde:
enum class PossibleChoices = {Option1, Option2, Option3}
template <PossibleChoices pc>
struct foo { };
Bu sınıfın üç olası örneği olabileceğini biliyoruz:
template class foo<PossibleChoices::Option1>;
template class foo<PossibleChoices::Option2>;
template class foo<PossibleChoices::Option3>;
Yukarıdakini bir çeviri birimine yerleştirin ve başlık dosyanızda sınıf tanımının altında extern anahtar sözcüğünü kullanın:
extern template class foo<PossibleChoices::Option1>;
extern template class foo<PossibleChoices::Option2>;
extern template class foo<PossibleChoices::Option3>;
Ortak bir örnek kümesiyle farklı testler derliyorsanız bu teknik size zaman kazandırabilir.
NOT: MPICH2 bu noktada açık örneklemeyi yoksayar ve her zaman tüm derleme birimlerinde örneklenmiş sınıfları derler.
Birlik derlemelerini kullanma
Birlik derlemelerinin ardındaki fikir, kullandığınız tüm .cc dosyalarını tek bir dosyaya dahil etmek ve bu dosyayı yalnızca bir kez derlemektir. Bu yöntemi kullanarak, farklı dosyaların ortak bölümlerini eski haline getirmekten kaçınabilirsiniz ve projenizde çok sayıda ortak dosya varsa, muhtemelen disk erişimlerinden de tasarruf edersiniz.
Örnek olarak, diyelim ki üç dosya var varsayalım foo1.cc
, foo2.cc
, foo3.cc
ve hepsi dahil tuple
dan STL . foo-all.cc
Şuna benzeyen bir tane oluşturabilirsiniz :
#include "foo1.cc"
#include "foo2.cc"
#include "foo3.cc"
Bu dosyayı yalnızca bir kez derlersiniz ve muhtemelen üç dosya arasındaki ortak örneklemeleri azaltırsınız. İyileştirmenin anlamlı olup olmadığını tahmin etmek genellikle zordur. Ancak açık bir gerçek, yapılarınızda paralelliğinizi kaybedeceğinizdir (artık üç dosyayı aynı anda derleyemezsiniz).
Ayrıca, bu dosyalardan herhangi biri çok fazla bellek alırsa, derleme bitmeden önce belleğiniz tükenebilir. GCC gibi bazı derleyicilerde, bellek yetersizliği nedeniyle derleyiciniz ICE (Dahili Derleyici Hatası) olabilir. Yani tüm artıları ve eksileri bilmiyorsanız bu tekniği kullanmayın.
Önceden derlenmiş başlıklar
Önceden derlenmiş üstbilgiler (PCH'ler), üstbilgi dosyalarınızı bir derleyici tarafından tanınan bir ara gösterimde derleyerek size çok zaman kazandırabilir. Önceden derlenmiş başlık dosyaları oluşturmak için, yalnızca başlık dosyanızı normal derleme komutunuzla derlemeniz gerekir. Örneğin, GCC'de:
$ g++ YOUR_HEADER.hpp
Bu , aynı klasörde YOUR_HEADER.hpp.gch file
( .gch
GCC'deki PCH dosyalarının uzantısıdır) oluşturur. Bu, YOUR_HEADER.hpp
başka bir dosyaya eklerseniz , derleyicinin daha önce aynı klasör YOUR_HEADER.hpp.gch
yerine kullanacağınız anlamına gelir YOUR_HEADER.hpp
.
Bu teknikle ilgili iki sorun vardır:
- Önceden derlenen başlık dosyalarının sabit olduğundan ve değişmeyeceğinden emin olmalısınız ( her zaman makefile'ınızı değiştirebilirsiniz )
- Derleme birimi başına yalnızca bir PCH ekleyebilirsiniz (derleyicilerin çoğunda). Bu, önceden derlenecek birden fazla başlık dosyanız varsa, bunları bir dosyaya (örn
all-my-headers.hpp
.) Dahil etmeniz gerektiği anlamına gelir . Ancak bu, yeni dosyayı tüm yerlere eklemeniz gerektiği anlamına gelir. Neyse ki, GCC'nin bu soruna bir çözümü var. Kullanın -include
ve yeni başlık dosyasını verin. Bu tekniği kullanarak farklı dosyaları virgülle ayırabilirsiniz.
Örneğin:
g++ foo.cc -include all-my-headers.hpp
Adsız veya anonim ad alanları kullanın
Adsız ad alanları (adsız ad alanları olarak da bilinir) oluşturulan ikili boyutları önemli ölçüde azaltabilir. Adsız ad alanları iç bağlantı kullanır, yani bu ad alanlarında oluşturulan semboller diğer TU (çeviri veya derleme birimleri) tarafından görülmez. Derleyiciler genellikle adsız ad alanları için benzersiz adlar oluşturur. Bu, foo.hpp dosyanız varsa:
namespace {
template <typename T>
struct foo { };
} // Anonymous namespace
using A = foo<int>;
Ve bu dosyayı iki TU'ya dahil ediyorsunuz (iki .cc dosyası ve ayrı olarak derleyin). İki foo şablonu örneği aynı olmayacaktır. Bu, Tek Tanımlama Kuralını (ODR) ihlal eder . Aynı nedenle, başlık dosyalarında adsız ad alanlarının kullanılması önerilmez. .cc
İkili dosyalarınızda sembollerin görünmesini önlemek için bunları dosyalarınızda kullanmaktan çekinmeyin . Bazı durumlarda, bir .cc
dosyanın tüm dahili ayrıntılarının değiştirilmesi , oluşturulan ikili boyutlarda% 10'luk bir azalma gösterdi.
Görünürlük seçeneklerini değiştirme
Daha yeni derleyicilerde sembollerinizi Dinamik Paylaşılan Nesneler'de (DSO'lar) görünür veya görünmez olarak seçebilirsiniz. İdeal olarak, görünürlüğün değiştirilmesi derleyici performansını, bağlantı zamanı optimizasyonlarını (LTO'lar) ve oluşturulan ikili boyutları artırabilir. GCC'deki STL başlık dosyalarına bakarsanız, yaygın olarak kullanıldığını görebilirsiniz. Görünürlük seçimlerini etkinleştirmek için kodunuzu işlev başına, sınıf başına, değişken başına ve daha da önemlisi derleyici başına değiştirmeniz gerekir.
Görünürlük yardımıyla, onları özel olarak değerlendirdiğiniz sembolleri oluşturulan paylaşılan nesnelerden gizleyebilirsiniz. GCC'de, varsayılan veya gizli -visibility
derleyicinizin seçeneğine geçerek sembollerin görünürlüğünü kontrol edebilirsiniz . Bu, bir anlamda isimsiz isim alanına benzer, ancak daha ayrıntılı ve müdahaleci bir şekilde.
Vaka başına görünürlükleri belirtmek isterseniz, işlevlerinize, değişkenlerinize ve sınıflarınıza aşağıdaki nitelikleri eklemeniz gerekir:
__attribute__((visibility("default"))) void foo1() { }
__attribute__((visibility("hidden"))) void foo2() { }
__attribute__((visibility("hidden"))) class foo3 { };
void foo4() { }
GCC'deki varsayılan görünürlük varsayılan (herkese açık) şeklindedir, yani yukarıdakileri paylaşılan bir kütüphane ( -shared
) yöntemi olarak derlerseniz foo2
ve sınıf foo3
diğer TU'larda görünmez ( foo1
ve foo4
görünür olur). Derleme yaparsanız -visibility=hidden
sadece foo1
görünür. Hatta foo4
saklı olurdu.
Görünürlük hakkında daha fazla bilgiyi GCC wiki'de okuyabilirsiniz .