Açık örnekleme, derleme sürelerini ve nesne boyutlarını azaltmaya izin verir
Bunlar sağlayabileceği en büyük kazanımlar. Aşağıdaki bölümlerde ayrıntılı olarak açıklanan aşağıdaki iki etkiden gelirler:
- Derleme araçlarının dahil olanları yeniden oluşturmasını önlemek için başlıklardan tanımları kaldırın
- nesne yeniden tanımlama
Tanımları başlıklardan kaldır
Açık örnekleme, tanımları .cpp dosyasında bırakmanıza izin verir.
Tanım başlık üzerindeyken ve onu değiştirdiğinizde, akıllı bir yapı sistemi, düzinelerce dosya olabilecek tüm dosyaları yeniden derleyerek derlemeyi dayanılmaz derecede yavaşlatır.
Tanımları .cpp dosyalarına koymak, harici kitaplıkların şablonu kendi yeni sınıflarıyla yeniden kullanamaması gibi bir dezavantaja sahiptir, ancak aşağıdaki "Dahil edilen başlıklardan tanımları kaldırın, ancak şablonları bir harici API'yi de kullanıma sunun" aşağıda bir geçici çözüm göstermektedir.
Aşağıdaki somut örneklere bakın.
Nesneyi yeniden tanımlama kazanımları: sorunu anlamak
Bir başlık dosyasında bir şablonu tamamen tanımlarsanız, bu başlığı içeren her bir derleme birimi, yapılan her farklı şablon argümanı kullanımı için şablonun kendi örtük kopyasını derler.
Bu, çok fazla gereksiz disk kullanımı ve derleme zamanı anlamına gelir.
İşte bu dosyalardaki kullanımı nedeniyle hem main.cpp
ve hem de notmain.cpp
örtük olarak tanımladığı somut bir örnek MyTemplate<int>
.
main.cpp
#include <iostream>
#include "mytemplate.hpp"
#include "notmain.hpp"
int main() {
std::cout << notmain() + MyTemplate<int>().f(1) << std::endl;
}
notmain.cpp
#include "mytemplate.hpp"
#include "notmain.hpp"
int notmain() { return MyTemplate<int>().f(1); }
mytemplate.hpp
#ifndef MYTEMPLATE_HPP
#define MYTEMPLATE_HPP
template<class T>
struct MyTemplate {
T f(T t) { return t + 1; }
};
#endif
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
int notmain();
#endif
GitHub yukarı akış .
Sembolleri şununla derleyin ve görüntüleyin nm
:
g++ -c -Wall -Wextra -std=c++11 -pedantic-errors -o notmain.o notmain.cpp
g++ -c -Wall -Wextra -std=c++11 -pedantic-errors -o main.o main.cpp
g++ -Wall -Wextra -std=c++11 -pedantic-errors -o main.out notmain.o main.o
echo notmain.o
nm -C -S notmain.o | grep MyTemplate
echo main.o
nm -C -S main.o | grep MyTemplate
Çıktı:
notmain.o
0000000000000000 0000000000000017 W MyTemplate<int>::f(int)
main.o
0000000000000000 0000000000000017 W MyTemplate<int>::f(int)
Buradan man nm
, bunun W
bir şablon işlevi olduğu için GCC'nin seçtiği zayıf sembol anlamına geldiğini görüyoruz . Zayıf sembol, için derlenen örtük olarak üretilen kodun MyTemplate<int>
her iki dosyada da derlendiği anlamına gelir .
Birden çok tanımla bağlantı anında patlamamasının nedeni , bağlayıcının birden çok zayıf tanımı kabul etmesi ve son çalıştırılabilir dosyaya koymak için bunlardan birini seçmesidir.
Çıktıdaki sayıların anlamı:
0000000000000000
: bölüm içindeki adres. Bu sıfır, şablonların otomatik olarak kendi bölümlerine yerleştirilmesidir.
0000000000000017
: onlar için oluşturulan kodun boyutu
Bunu biraz daha net görebiliriz:
objdump -S main.o | c++filt
biten:
Disassembly of section .text._ZN10MyTemplateIiE1fEi:
0000000000000000 <MyTemplate<int>::f(int)>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 89 7d f8 mov %rdi,-0x8(%rbp)
c: 89 75 f4 mov %esi,-0xc(%rbp)
f: 8b 45 f4 mov -0xc(%rbp),%eax
12: 83 c0 01 add $0x1,%eax
15: 5d pop %rbp
16: c3 retq
ve çözülmemeye karar veren _ZN10MyTemplateIiE1fEi
ezilmiş adıdır .MyTemplate<int>::f(int)>
c++filt
Böylece, her bir yöntem somutlaştırması için ayrı bir bölümün oluşturulduğunu ve her birinin tabii ki nesne dosyalarında yer kapladığını görüyoruz.
Nesneyi yeniden tanımlama problemine çözümler
Bu sorun, açık örnekleme kullanılarak ve aşağıdakilerden biri kullanılarak önlenebilir:
tanımlamayı hpp üzerinde tutun ve extern template
açıkça başlatılacak türler için hpp'yi ekleyin .
Açıklandığı gibi: extern şablonunun (C ++ 11) kullanılması extern template
, tamamen tanımlanmış bir şablonun, açık somutlaştırmamız dışında derleme birimleri tarafından somutlaştırılmasını engeller. Bu şekilde, nihai nesnelerde yalnızca açık somutlaştırmamız tanımlanacaktır:
mytemplate.hpp
#ifndef MYTEMPLATE_HPP
#define MYTEMPLATE_HPP
template<class T>
struct MyTemplate {
T f(T t) { return t + 1; }
};
extern template class MyTemplate<int>;
#endif
mytemplate.cpp
#include "mytemplate.hpp"
template class MyTemplate<int>;
main.cpp
#include <iostream>
#include "mytemplate.hpp"
#include "notmain.hpp"
int main() {
std::cout << notmain() + MyTemplate<int>().f(1) << std::endl;
}
notmain.cpp
#include "mytemplate.hpp"
#include "notmain.hpp"
int notmain() { return MyTemplate<int>().f(1); }
Dezavantaj:
- Yalnızca başlık kitaplığıysanız, harici projeleri kendi açık örneğini yapmaya zorlarsınız. Yalnızca başlık içeren bir kitaplık değilseniz, bu çözüm muhtemelen en iyisidir.
- şablon türü kendi projenizde tanımlanmışsa ve yerleşik bir benzeri değilse
int
, bunun için dahil etmeyi başlığa eklemeye zorlanmışsınız gibi görünür, ileriye dönük bir bildirim yeterli değildir: extern şablonu ve eksik türler Bu, başlık bağımlılıklarını artırır biraz.
tanımı cpp dosyasında taşımak, sadece bildirimi hpp üzerinde bırakın, yani orijinal örneği şu şekilde değiştirin:
mytemplate.hpp
#ifndef MYTEMPLATE_HPP
#define MYTEMPLATE_HPP
template<class T>
struct MyTemplate {
T f(T t);
};
#endif
mytemplate.cpp
#include "mytemplate.hpp"
template<class T>
T MyTemplate<T>::f(T t) { return t + 1; }
template class MyTemplate<int>;
Dezavantajı: harici projeler şablonunuzu kendi türleriyle kullanamaz. Ayrıca tüm türleri açık bir şekilde somutlaştırmak zorunda kalırsınız. Ama belki de bu, programcıların unutmayacağı zamandan beri bir artıdır.
tanımlamayı hpp'de tutun ve extern template
her dahil edilene ekleyin :
mytemplate.cpp
#include "mytemplate.hpp"
template class MyTemplate<int>;
main.cpp
#include <iostream>
#include "mytemplate.hpp"
#include "notmain.hpp"
extern template class MyTemplate<int>;
int main() {
std::cout << notmain() + MyTemplate<int>().f(1) << std::endl;
}
notmain.cpp
#include "mytemplate.hpp"
#include "notmain.hpp"
extern template class MyTemplate<int>;
int notmain() { return MyTemplate<int>().f(1); }
Dezavantajı: tüm dahil olanlar extern
CPP dosyalarına eklemek zorundadır , bu da programcıların yapmayı unutması muhtemeldir.
Bu çözümlerden herhangi biri ile nm
artık şunları içerir:
notmain.o
U MyTemplate<int>::f(int)
main.o
U MyTemplate<int>::f(int)
mytemplate.o
0000000000000000 W MyTemplate<int>::f(int)
bu yüzden sadece istendiği gibi mytemplate.o
bir derlemeye sahip olduğunu görüyoruz MyTemplate<int>
, while notmain.o
ve main.o
değil çünkü U
tanımsız anlamına geliyor.
Tanımları dahil edilen başlıklardan kaldırın, ancak aynı zamanda şablonları yalnızca başlık içeren bir kitaplıkta harici bir API ortaya çıkarın
Kitaplığınız yalnızca başlık değilse, extern template
yöntem çalışacaktır, çünkü projeleri kullanmak yalnızca açık şablon somutlaştırmasının nesnesini içerecek olan nesne dosyanıza bağlanacaktır.
Ancak, yalnızca başlık kitaplıkları için, ikisini birden istiyorsanız:
- projenizin derlemesini hızlandırın
- Başlıkları, başkalarının kullanması için harici bir kitaplık API'si olarak göster
o zaman aşağıdakilerden birini deneyebilirsiniz:
-
mytemplate.hpp
: şablon tanımı
mytemplate_interface.hpp
: şablon bildirimi yalnızca tanımlarla eşleşen, tanım mytemplate_interface.hpp
yok
mytemplate.cpp
: dahil edin mytemplate.hpp
ve açık anlık bildirimler yapın
main.cpp
ve kod tabanındaki diğer her yerde: dahil etme mytemplate_interface.hpp
, değilmytemplate.hpp
-
mytemplate.hpp
: şablon tanımı
mytemplate_implementation.hpp
: örneklenecek her sınıfı içerir mytemplate.hpp
ve eklerextern
mytemplate.cpp
: dahil edin mytemplate.hpp
ve açık anlık bildirimler yapın
main.cpp
ve kod tabanındaki diğer her yerde: dahil etme mytemplate_implementation.hpp
, değilmytemplate.hpp
Veya daha da iyisi birden çok başlık için: klasörünüzün içinde bir intf
/ impl
klasör oluşturun includes/
ve mytemplate.hpp
her zaman ad olarak kullanın .
mytemplate_interface.hpp
Yaklaşım şu şekildedir:
mytemplate.hpp
#ifndef MYTEMPLATE_HPP
#define MYTEMPLATE_HPP
#include "mytemplate_interface.hpp"
template<class T>
T MyTemplate<T>::f(T t) { return t + 1; }
#endif
mytemplate_interface.hpp
#ifndef MYTEMPLATE_INTERFACE_HPP
#define MYTEMPLATE_INTERFACE_HPP
template<class T>
struct MyTemplate {
T f(T t);
};
#endif
mytemplate.cpp
#include "mytemplate.hpp"
template class MyTemplate<int>;
main.cpp
#include <iostream>
#include "mytemplate_interface.hpp"
int main() {
std::cout << MyTemplate<int>().f(1) << std::endl;
}
Derleyin ve çalıştırın:
g++ -c -Wall -Wextra -std=c++11 -pedantic-errors -o mytemplate.o mytemplate.cpp
g++ -c -Wall -Wextra -std=c++11 -pedantic-errors -o main.o main.cpp
g++ -Wall -Wextra -std=c++11 -pedantic-errors -o main.out main.o mytemplate.o
Çıktı:
2
Ubuntu 18.04'te test edilmiştir.
C ++ 20 modülleri
https://en.cppreference.com/w/cpp/language/modules
Bu özelliğin, kullanıma sunulduğunda en iyi kurulumu sağlayacağını düşünüyorum, ancak henüz GCC 9.2.1'imde mevcut olmadığı için henüz kontrol etmedim.
Hızlandırmayı / disk kaydetmeyi elde etmek için yine de açık örnekleme yapmanız gerekecek, ancak en azından "Dahil edilen başlıklardan tanımları kaldırın, ancak aynı zamanda şablonları yaklaşık 100 kez kopyalamayı gerektirmeyen harici bir API'yi de ortaya çıkarın" için mantıklı bir çözümümüz olacak.
Beklenen kullanım (açık belirtme olmadan, tam sözdiziminin nasıl olacağından emin değilsiniz, bkz: C ++ 20 modülleri ile şablon açık örnekleme nasıl kullanılır? )
helloworld.cpp
export module helloworld;
import <iostream>;
template<class T>
export void hello(T t) {
std::cout << t << std::end;
}
main.cpp
import helloworld;
int main() {
hello(1);
hello("world");
}
ve sonra derlemeden bahsedilir https://quuxplusone.github.io/blog/2019/11/07/modular-hello-world/
clang++ -std=c++2a -c helloworld.cpp -Xclang -emit-module-interface -o helloworld.pcm
clang++ -std=c++2a -c -o helloworld.o helloworld.cpp
clang++ -std=c++2a -fprebuilt-module-path=. -o main.out main.cpp helloworld.o
Dolayısıyla, bundan clang'ın şablon arayüzü + uygulamasını sihire çıkarabildiğini görüyoruz helloworld.pcm
, ki bu da kaynağın bazı LLVM ara temsillerini içermelidir: C ++ modül sisteminde şablonlar nasıl işlenir? bu da şablon spesifikasyonunun gerçekleşmesine izin verir.
Şablon oluşturmadan çok şey kazanıp kazanmayacağını görmek için yapınızı hızlı bir şekilde nasıl analiz edebilirsiniz?
Öyleyse, karmaşık bir projeniz var ve şablon somutlaştırmanın, tam yeniden düzenleme yapmadan önemli kazançlar sağlayıp sağlamayacağına karar vermek mi istiyorsunuz?
Aşağıdaki analiz, aşağıdakilerden bazı fikirleri ödünç alarak, deney yaparken ilk önce yeniden düzenleme yapmak için karar vermenize veya en azından en umut verici nesneleri seçmenize yardımcı olabilir: C ++ nesne dosyam çok büyük
# List all weak symbols with size only, no address.
find . -name '*.o' | xargs -I{} nm -C --size-sort --radix d '{}' |
grep ' W ' > nm.log
# Sort by symbol size.
sort -k1 -n nm.log -o nm.sort.log
# Get a repetition count.
uniq -c nm.sort.log > nm.uniq.log
# Find the most repeated/largest objects.
sort -k1,2 -n nm.uniq.log -o nm.uniq.sort.log
# Find the objects that would give you the most gain after refactor.
# This gain is calculated as "(n_occurences - 1) * size" which is
# the size you would gain for keeping just a single instance.
# If you are going to refactor anything, you should start with the ones
# at the bottom of this list.
awk '{gain = ($1 - 1) * $2; print gain, $0}' nm.uniq.sort.log |
sort -k1 -n > nm.gains.log
# Total gain if you refactored everything.
awk 'START{sum=0}{sum += $1}END{print sum}' nm.gains.log
# Total size. The closer total gain above is to total size, the more
# you would gain from the refactor.
awk 'START{sum=0}{sum += $1}END{print sum}' nm.log
Rüya: bir şablon derleyici önbelleği
Bence nihai çözüm şununla inşa edebilirsek olurdu:
g++ --template-cache myfile.o file1.cpp
g++ --template-cache myfile.o file2.cpp
ve daha sonra myfile.o
önceden derlenmiş şablonları dosyalarda otomatik olarak yeniden kullanır.
Bu, ekstra CLI seçeneğini derleme sisteminize geçirmenin yanı sıra programcılar için 0 ekstra çaba anlamına gelir.
Açık şablon somutlaştırmanın ikincil bir avantajı: IDE'lerin şablon örneklemelerini listelemesine yardım edin
Eclipse gibi bazı IDE'lerin "kullanılan tüm şablon örneklerinin bir listesini" çözemediğini buldum.
Örneğin, şablonlu bir kodun içindeyseniz ve şablonun olası değerlerini bulmak istiyorsanız, kurucu kullanımlarını tek tek bulmanız ve olası türleri tek tek çıkarmanız gerekir.
Ancak Eclipse 2020-03'te, sınıf adında Tüm kullanımları bul (Ctrl + Alt + G) araması yaparak açıkça başlatılmış şablonları kolayca listeleyebilirim, bu da beni örneğin:
template <class T>
struct AnimalTemplate {
T animal;
AnimalTemplate(T animal) : animal(animal) {}
std::string noise() {
return animal.noise();
}
};
to:
template class AnimalTemplate<Dog>;
İşte bir demo: https://github.com/cirosantilli/ide-test-projects/blob/e1c7c6634f2d5cdeafd2bdc79bcfbb2057cb04c4/cpp/animal_template.hpp#L15
IDE dışında kullanabileceğiniz başka bir guerrila tekniği nm -C
, son çalıştırılabilir dosya üzerinde çalıştırmak ve şablon adını grep etmektir:
nm -C main.out | grep AnimalTemplate
bu, doğrudan Dog
örneklerden biri olduğuna işaret ediyor :
0000000000004dac W AnimalTemplate<Dog>::noise[abi:cxx11]()
0000000000004d82 W AnimalTemplate<Dog>::AnimalTemplate(Dog)
0000000000004d82 W AnimalTemplate<Dog>::AnimalTemplate(Dog)