網(wǎng)站開發(fā)需要有什么證書搜索引擎排名查詢工具
4.4.3.?X86Subtarget
在X86TargetMachine構(gòu)造函數(shù)的105行調(diào)用了X86Subtarget構(gòu)造函數(shù)來創(chuàng)建具體的目標機器對象。
4.4.3.1.?FMV的支持(v7.0)
V7.0將具體目標機器對象的生成推遲到第一次調(diào)用getSubtarget ()時才創(chuàng)建。不過,為了方便起見,我們在這里把v7.0的實現(xiàn)也一起看了。在v7.0里getSubtarget ()是這樣的:
122???? ? template <typename STC> const STC &getSubtarget(const Function &F) const {
123???? ??? return *static_cast<const STC*>(getSubtargetImpl(F));
124???? ? }
目標機器對象的創(chuàng)建由目標機器的getSubtargetImpl()完成。V7.0的這個改動是為了支持稱為多版本函數(shù)的新特性。關(guān)于這個新特性,可以參考這個網(wǎng)址Function multi-versioning in GCC?6 [LWN.net],下面是它的翻譯(關(guān)于LLVM有這么一篇論文)。
CPU架構(gòu)隨著演進通常會獲得有趣的新指令,但應用程序開發(fā)者通常發(fā)現(xiàn)利用這些指令是困難的。不愿意失去后向兼容是阻礙開發(fā)人員使用更新的計算架構(gòu)的主要障礙之一。函數(shù)多版本化(function multi-versioning,FMV),首先出現(xiàn)在gcc 4.8,是擁有函數(shù)多個實現(xiàn)的方式,每個實現(xiàn)使用不同架構(gòu)特定的指令集擴展。Gcc 6引入了對FMV的修改,更容易向應用程序代碼引入基于架構(gòu)的優(yōu)化。 盡管gcc與內(nèi)核的新版本嘗試在平臺面市前公開使用新架構(gòu)特性的工具,但開發(fā)人員難以開始使用這些工具。當前,C開發(fā)者有幾個選擇:
通常使用新架構(gòu)技術(shù)的好處足以壓倒集成的挑戰(zhàn)。例如,打開Intel先進向量擴展(AVX)會顯著優(yōu)化數(shù)學代碼。AVX的第二個版本(AVX2),在第4代,也稱為Haswell的Intel Core處理器里引入,是一個選擇。在科學計算領(lǐng)域AVX2的好處廣為人知。OpenBLAS庫使用AVX2給予了像R語言這樣的項目,執(zhí)行上2倍的加速;它也在Python科學庫里產(chǎn)生了顯著的提高。這些性能提升是通過使用256比特指令、浮點融合乘加指令以及gather操作,使每秒浮點操作(FLOPS)加倍,獲得的。 不過,使用向量擴展(VX)技術(shù)意味著大量的開發(fā)、部署以及維護性工作。維護多個版本二進制文件的想法(一個架構(gòu)一個)阻止開發(fā)者以及發(fā)行版本支持這些特性。 為多個架構(gòu)優(yōu)化某些關(guān)鍵函數(shù),當運行時二進制文件檢測到CPU能力時執(zhí)行它們,會更好嗎?這樣做的一個特性,FMV,實際上自gcc 4.8以來就存在,但僅用于C++。gcc 4.8里的FMV使得開發(fā)者容易指定一個函數(shù)的多個版本;每個針對特定目標機器指令集優(yōu)化。Gcc負責創(chuàng)建執(zhí)行函數(shù)正確版本所需的分發(fā)代碼。 要在C++代碼里使用FMV,用戶要指定函數(shù)的多個版本。例如,在gcc 4.8 FMV文檔里展示的代碼: ??? __attribute__ ((target ("sse4.2"))) ??? int foo(){ ?????? // foo version for SSE4.2 ?????? return 1; ??? } ??? __attribute__ ((target ("arch=atom"))) ??? int foo(){ ?????? // foo version for the Intel Atom processor ?????? return 2; ??? } ??? int main() { ?????? int (*p)() = &foo; ?????? assert((*p)() == foo()); ?????? return 0; ??? } Target()指示將對指令集擴展(如sse4.2)或指定架構(gòu)(如arch=atom)編譯函數(shù)。 這里,對每個函數(shù),開發(fā)者需要為每個目標創(chuàng)建特殊的函數(shù)與代碼。這將要求代碼里額外的開銷;在FMV程序里代碼行數(shù)的增加,使得它更難以管理與維護。 幸好,gcc 6解決了這個問題:它使用單個屬性來定義要支持的最小架構(gòu)集,在C及C++代碼里支持FMV。這使得開發(fā)可以利用增強指令的Linux應用變得容易,無需為每個目標復制函數(shù)。 通過FMV來利用AVX的簡單例子是使用數(shù)組加法(這個例子是array_addition.c): ??? #define MAX 1000000 ??? int a[256], b[256], c[256]; ??? __attribute__((target_clones("avx2","arch=atom","default"))) ??? void foo(){ ??????? int i,x; ?????? ?????? for (x=0; x<MAX; x++){ ?????? ??? for (i=0; i<256; i++){ ????????? ??? a[i] = b[i] + c[i]; ?????? ??? } ?????? } ??? } ??? int main() { ??????? foo(); ??????? return 0; ??? } 正如我們可以看到的,使用target_clones()指示支持架構(gòu)的選擇是相當簡單的。開發(fā)者僅需要選擇架構(gòu)或要支持指令集擴展的最小集:AVX2、Intel Atom、AMD或幾乎任何gcc從命令行接受的架構(gòu)選項。編譯器將創(chuàng)建函數(shù)面向指定指令集的多個版本,并在運行時選擇正確的版本。 最終,這個代碼的object dump有對每個架構(gòu)最優(yōu)的匯編指令。例如: 非AVX代碼(Atom): ??? add??? %eax,%edx AVX: ??? vpaddd 0x0(%rax),%xmm0,%xmm0 AVX2: ??? vpaddd (%r9,%rax,1),%ymm0,%ymm0 注意FMV的新實現(xiàn)向array_addition.c提供了使用Intel AVX、AVX2甚至Atom平臺的寄存器與指令的能力。這個能力增大了應用程序可以不出現(xiàn)非法指令錯誤運行的平臺的范圍。 在gcc 6以前,告訴編譯器使用Intel AVX2指令將把二進制的兼容性限制在Haswell和更新的處理器。通過FMV里新加的特性,編譯器還可以產(chǎn)生AVX優(yōu)化的代碼;在運行時,將自動確保僅使用合適的版本。換而言之,當二進制運行在Haswekk或更新的CPU上時,將使用Haswell特定的優(yōu)化;當同一個二進制在前Haswell世代處理器上運行時,它將回退到使用舊處理器支持的標準指令。 CPUID選擇 在gcc 4.8里,FMV有一個分發(fā)優(yōu)先級,而不是一個CPUID選擇。分發(fā)次序基于目標屬性對每個函數(shù)版本排序。帶有更先進特性的函數(shù)版本有更高的優(yōu)先級。例如,面向AVX2的版本比面向SSE2的版本優(yōu)先級更高。 為了保持分發(fā)的低代價,使用了間接函數(shù)(ifunc)機制。該機制是GNU工具鏈的一個特性,它允許開發(fā)者創(chuàng)建給定函數(shù)的多個實現(xiàn),在運行時使用一個解析器函數(shù)在其中選擇。在啟動早期這個解析器函數(shù)由動態(tài)載入器調(diào)用,決定應用程序使用哪個實現(xiàn)。一旦做出了實現(xiàn)選擇,就固定下來,在這個過程的生命期里不變了。 在gcc 6中,解析器檢查CPUID并調(diào)用相應的函數(shù)。它對每個二進制執(zhí)行文件都做一次。因此當存在對FMV函數(shù)的多個調(diào)用時,僅第一個調(diào)用會執(zhí)行CPUID比較;后續(xù)調(diào)用將通過一個指針找到要求的版本。這個技術(shù)已經(jīng)用于幾乎所有的glibc函數(shù)。例如,glibc對每個架構(gòu)都優(yōu)化了memcpy(),因此當調(diào)用時,glibc將調(diào)用恰當優(yōu)化的memcpy()。 代碼大小影響 FMV將增加二進制代碼的大小,但這個影響可以最小化。代碼大小的增加依賴于應用FMV的函數(shù)有多大,以及要求版本的數(shù)量。如果最初二進制代碼大小是C,N是請求的版本數(shù)(包括缺省),R是這些函數(shù)占整個應用程序代碼的比例,新代碼的大小將是: ??? (1 - R) * C + R * C * N 如果一個應用程序最熱代碼占總大小的1%,且應用FMV支持三個架構(gòu)(缺省,sse4.2,avx2),代碼大小總共增加2%。在考慮今天的儲存容量時,這是相當小的影響。但這種影響必須基于部署模型來考慮。性能、維護性與增加的二進制代碼間存在權(quán)衡,因此對某種類型的部署FMV可能不是正確的選擇(比如物聯(lián)網(wǎng)設備)。 結(jié)果 下表展示了在不同處理器上,使用不同gcc標記運行array_addition.c的執(zhí)行時間: 執(zhí)行時間(ms)
FMV版本使用下面的指示: ? ?__attribute__((target_clones("avx2","arch=atom","default"))) SIGILL項表示對某些組合是非法指令。缺省的CFLAGS(不是特別值得注意),配置作為Clear Linux for Intel Architecture項目部分說明。 實例 今天,越來越多行業(yè)部門從基于云的科學計算中獲益。這些部門包括化工、財務以及分析應用程序。其中一個更受歡迎的科學計算庫是用于Python的NumPy庫。它包括了對大的、多維數(shù)組與矩陣的支持。它還有用于線性代數(shù)、傅里葉變換以及隨機數(shù)生成等等的特性。 在一個諸如NumPy的科學庫里使用FMV技術(shù)的好處通常是它得到良好的理解與接受。如果沒有啟用向量化,SIMD寄存器里許多未用的空間浪費了。如果啟用向量化,在一條指令里編譯器使用額外的寄存器執(zhí)行更多的操作(比如我們例子里更多整數(shù)加法)。 由于FMV技術(shù)性能的提升(運行在帶有AVX2指令的Haswell機器上),對科學計算內(nèi)容可以到達3%。我們使用運行在1.8GHz的Skylake系統(tǒng)上的OpenBenchmarking.org numpy-1.0.2,使用FMV運行時間是8400秒,而在使用-O3編譯時是8600秒。 性能提升歸功于從向量化受益的NumPy代碼里的函數(shù)。為了檢測這些函數(shù),gcc提供了標記-fopt-info-vec。這個標記用于檢測向量化候選函數(shù)。例如,以這個標記構(gòu)建NumPy將告訴我們文件fftpack.c有可以使用向量化的代碼: ?? ?numpy/fft/fftpack.c:813:7: note: loop peeled for vectorization to enhance alignment 查看NumPy源代碼顯示radfg()函數(shù),這是NumPy里支持的快速傅里葉變換的一部分,執(zhí)行大量可以使用AVX優(yōu)化的數(shù)組加法。NumPy的補丁還未升級,但指日可待。 |
250???? const X86Subtarget *
251???? X86TargetMachine::getSubtargetImpl(const Function &F) const {
252???? ? Attribute CPUAttr = F.getFnAttribute("target-cpu");
253???? ? Attribute FSAttr = F.getFnAttribute("target-features");
254????
255???? ? StringRef CPU = !CPUAttr.hasAttribute(Attribute::None)
256???? ????????????????????? ? CPUAttr.getValueAsString()
257???? ????????????????????? : (StringRef)TargetCPU;
258???? ? StringRef FS = !FSAttr.hasAttribute(Attribute::None)
259???? ???????????????????? ? FSAttr.getValueAsString()
260???? ???????????????????? : (StringRef)TargetFS;
261????
262???? ? SmallString<512> Key;
263???? ? Key.reserve(CPU.size() + FS.size());
264???? ? Key += CPU;
265???? ? Key += FS;
266????
267???? ? // FIXME: This is related to the code below to reset the target options,
268???? ? // we need to know whether or not the soft float flag is set on the
269???? ? // function before we can generate a subtarget. We also need to use
270???? ? // it as a key for the subtarget since that can be the only difference
271???? ? // between two functions.
272???? ? bool SoftFloat =
273???? ????? F.getFnAttribute("use-soft-float").getValueAsString() == "true";
274???? ? // If the soft float attribute is set on the function turn on the soft float
275???? ? // subtarget feature.
276???? ? if (SoftFloat)
277???? ??? Key += FS.empty() ? "+soft-float" : ",+soft-float";
278????
279???? ? // Keep track of the key width after all features are added so we can extract
280???? ? // the feature string out later.
281???? ? unsigned CPUFSWidth = Key.size();
282????
283???? ? // Extract prefer-vector-width attribute.
284???? ? unsigned PreferVectorWidthOverride = 0;
285???? ? if (F.hasFnAttribute("prefer-vector-width")) {
286???? ??? StringRef Val = F.getFnAttribute("prefer-vector-width").getValueAsString();
287???? ??? unsigned Width;
288???? ??? if (!Val.getAsInteger(0, Width)) {
289???? ????? Key += ",prefer-vector-width=";
290???? ????? Key += Val;
291???? ????? PreferVectorWidthOverride = Width;
292???? ??? }
293???? ? }
294????
295???? ? // Extract required-vector-width attribute.
296???? ? unsigned RequiredVectorWidth = UINT32_MAX;
297???? ? if (F.hasFnAttribute("required-vector-width")) {
298???? ??? StringRef Val = F.getFnAttribute("required-vector-width").getValueAsString();
299???? ??? unsigned Width;
300???? ??? if (!Val.getAsInteger(0, Width)) {
301???? ????? Key += ",required-vector-width=";
302???? ????? Key += Val;
303???? ????? RequiredVectorWidth = Width;
304???? ??? }
305???? ? }
306????
307???? ? // Extracted here so that we make sure there is backing for the StringRef. If
308???? ? // we assigned earlier, its possible the SmallString reallocated leaving a
309???? ? // dangling StringRef.
310???? ? FS = Key.slice(CPU.size(), CPUFSWidth);
311????
312???? ? auto &I = SubtargetMap[Key];
313???? ? if (!I) {
314???? ?? ?// This needs to be done before we create a new subtarget since any
315???? ??? // creation will depend on the TM and the code generation flags on the
316???? ??? // function that reside in TargetOptions.
317???? ??? resetTargetOptions(F);
318???? ??? I = llvm::make_unique<X86Subtarget>(TargetTriple, CPU, FS, *this,
319???? ??????????????????????????????????????? Options.StackAlignmentOverride,
320???? ??????????????????????????????????????? PreferVectorWidthOverride,
321???? ??????????????? ????????????????????????RequiredVectorWidth);
322???? ? }
323???? ? return I.get();
324???? }
MFV需要多個目標機器可用,因此現(xiàn)在使用容器SubtargetMap(類型mutable StringMap<std:: unique_ptr<X86Subtarget>>)來保存多個X86Subtarget實例,鍵值是描述目標CPU以及各方面特性的字符串,這個字符串確保唯一。
317行的resetTargetOptions()根據(jù)當前函數(shù)的屬性改寫由InitTargetOptionsFromCodeGenFlags()等根據(jù)編譯命令行設置的屬性。
在318行創(chuàng)建X86Subtarget實例。
289???? X86Subtarget::X86Subtarget(const Triple &TT, const std::string &CPU,
290???? ?????????????????????????? const std::string &FS, const X86TargetMachine &TM,
291???? ?????????????????????????? unsigned StackAlignOverride)
292???? ??? : X86GenSubtargetInfo(TT, CPU, FS), X86ProcFamily(Others),
293???? ????? PICStyle(PICStyles::None), TargetTriple(TT),
294???? ????? StackAlignOverride(StackAlignOverride),
295???? ????? In64BitMode(TargetTriple.getArch() == Triple::x86_64),
296???? ????? In32BitMode(TargetTriple.getArch() == Triple::x86 &&
297???? ????????????????? TargetTriple.getEnvironment() != Triple::CODE16),
298???? ????? In16BitMode(TargetTriple.getArch() == Triple::x86 &&
299???? ????????????????? TargetTriple.getEnvironment() == Triple::CODE16),
300???? ????? TSInfo(), InstrInfo(initializeSubtargetDependencies(CPU, FS)),
301???? ????? TLInfo(TM, *this), FrameLowering(*this, getStackAlignment()) {
302???? ? // Determine the PICStyle based on the target selected.
303???? ? if (TM.getRelocationModel() == Reloc::Static !isPositionIndependent()) {
304???? ??? // Unless we're in PIC or DynamicNoPIC mode, set the PIC style to None.
305???? ??? setPICStyle(PICStyles::None);
306???? ? } else if (is64Bit()) {
307???? ??? // PIC in 64 bit mode is always rip-rel.
308???? ??? setPICStyle(PICStyles::RIPRel);
309???? ? } else if (isTargetCOFF()) {
310???? ??? setPICStyle(PICStyles::None);
311???? ? } else if (isTargetDarwin()) {
312???? ??? if (TM.getRelocationModel() == Reloc::PIC_)?????????????????????????????????????????????????????????????????????? ? v7.0刪除
313???? ????? setPICStyle(PICStyles::StubPIC);
314???? ??? else {
315???? ????? assert(TM.getRelocationModel() == Reloc::DynamicNoPIC);
316???? ????? setPICStyle(PICStyles::StubDynamicNoPIC);
317???? ??? }
318???? ? } else if (isTargetELF()) {
319???? ??? setPICStyle(PICStyles::GOT);
320???? ? }
? CallLoweringInfo.reset(new X86CallLowering(*getTargetLowering()));?????????????????????????? ? v7.0增加
? Legalizer.reset(new X86LegalizerInfo(*this, TM));
? auto *RBI = new X86RegisterBankInfo(*getRegisterInfo());
? RegBankInfo.reset(RBI);
? InstSelector.reset(createX86InstructionSelector(TM, *this, *RBI));
321???? }
基類X86GenSubtargetInfo的構(gòu)造函數(shù)是TableGen生成的,前面我們已經(jīng)看到,它將MC層的一組指針指向X86目標機器特定的參數(shù)。300行的成員TSInfo的類型是X86SelectionDAGInfo,目標機器通過它可以提供對memcpy、memmove、memset、memcmp、memchr、strcpy、strcmp、strlen,這些操作的專屬處理代碼(v7.0刪除這個調(diào)用)。
303行的isPositionIndependent()檢查使用的重定位模型是否為Reloc::PIC_,這些重定位模型用于動態(tài)庫的生成。V7.0簡化為這幾種:StubPIC(i386-darwin的pic),GOT(全局對象表,32位elf的pic),RIPRel(相對RIP,64位elf的pic),None(沒有使用pic)。位置無關(guān)代碼參考有關(guān)資料(如《C++高級編譯》)。