LLVM学习笔记(60)

4.4.3. X86Subtarget

在X86TargetMachine构造函数的105行调用了X86Subtarget构造函数来创建具体的目标机器对象。

4.4.3.1. FMV的支持(v7.0

V7.0将具体目标机器对象的生成推迟到第一次调用getSubtarget ()时才创建。不过,为了方便起见,我们在这里把v7.0的实现也一起看了。在v7.0getSubtarget ()是这样的:

122       template <typename STC> const STC &getSubtarget(const Function &F) const {

123         return *static_cast<const STC*>(getSubtargetImpl(F));

124       }

目标机器对象的创建由目标机器的getSubtargetImpl()完成。V7.0的这个改动是为了支持称为多版本函数的新特性。关于这个新特性,可以参考这个网址Function multi-versioning in GCC 6 [LWN.net],下面是它的翻译(关于LLVM有这么一篇论文)。

CPU架构随着演进通常会获得有趣的新指令,但应用程序开发者通常发现利用这些指令是困难的。不愿意失去后向兼容是阻碍开发人员使用更新的计算架构的主要障碍之一。函数多版本化(function multi-versioningFMV),首先出现在gcc 4.8,是拥有函数多个实现的方式,每个实现使用不同架构特定的指令集扩展。Gcc 6引入了对FMV的修改,更容易向应用程序代码引入基于架构的优化。

尽管gcc与内核的新版本尝试在平台面市前公开使用新架构特性的工具,但开发人员难以开始使用这些工具。当前,C开发者有几个选择:

  • 编写自己代码的多个版本,每个面向不同的指令集扩展;这要求他们还要手动处理这些版本的运行时分发。
  • 生成二进制文件的多个版本,每个面向不同的平台。
  • 选择一个最低的硬件要求,不使用新平台上的技术。

通常使用新架构技术的好处足以压倒集成的挑战。例如,打开Intel先进向量扩展(AVX)会显著优化数学代码。AVX的第二个版本(AVX2),在第4代,也称为HaswellIntel Core处理器里引入,是一个选择。在科学计算领域AVX2的好处广为人知。OpenBLAS库使用AVX2给予了像R语言这样的项目,执行上2倍的加速;它也在Python科学库里产生了显著的提高。这些性能提升是通过使用256比特指令、浮点融合乘加指令以及gather操作,使每秒浮点操作(FLOPS)加倍,获得的。

不过,使用向量扩展(VX)技术意味着大量的开发、部署以及维护性工作。维护多个版本二进制文件的想法(一个架构一个)阻止开发者以及发行版本支持这些特性。

为多个架构优化某些关键函数,当运行时二进制文件检测到CPU能力时执行它们,会更好吗?这样做的一个特性,FMV,实际上自gcc 4.8以来就存在,但仅用于C++gcc 4.8里的FMV使得开发者容易指定一个函数的多个版本;每个针对特定目标机器指令集优化。Gcc负责创建执行函数正确版本所需的分发代码。

要在C++代码里使用FMV,用户要指定函数的多个版本。例如,在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)或指定架构(如arch=atom)编译函数。

这里,对每个函数,开发者需要为每个目标创建特殊的函数与代码。这将要求代码里额外的开销;在FMV程序里代码行数的增加,使得它更难以管理与维护。

幸好,gcc 6解决了这个问题:它使用单个属性来定义要支持的最小架构集,在CC++代码里支持FMV。这使得开发可以利用增强指令的Linux应用变得容易,无需为每个目标复制函数。

通过FMV来利用AVX的简单例子是使用数组加法(这个例子是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()指示支持架构的选择是相当简单的。开发者仅需要选择架构或要支持指令集扩展的最小集:AVX2Intel AtomAMD或几乎任何gcc从命令行接受的架构选项。编译器将创建函数面向指定指令集的多个版本,并在运行时选择正确的版本。

最终,这个代码的object dump有对每个架构最优的汇编指令。例如:

AVX代码(Atom):

    add    %eax,%edx

AVX:

    vpaddd 0x0(%rax),%xmm0,%xmm0

AVX2:

    vpaddd (%r9,%rax,1),%ymm0,%ymm0

注意FMV的新实现向array_addition.c提供了使用Intel AVXAVX2甚至Atom平台的寄存器与指令的能力。这个能力增大了应用程序可以不出现非法指令错误运行的平台的范围。

gcc 6以前,告诉编译器使用Intel AVX2指令将把二进制的兼容性限制在Haswell和更新的处理器。通过FMV里新加的特性,编译器还可以产生AVX优化的代码;在运行时,将自动确保仅使用合适的版本。换而言之,当二进制运行在Haswekk或更新的CPU上时,将使用Haswell特定的优化;当同一个二进制在前Haswell世代处理器上运行时,它将回退到使用旧处理器支持的标准指令。

CPUID选择

gcc 4.8里,FMV有一个分发优先级,而不是一个CPUID选择。分发次序基于目标属性对每个函数版本排序。带有更先进特性的函数版本有更高的优先级。例如,面向AVX2的版本比面向SSE2的版本优先级更高。

为了保持分发的低代价,使用了间接函数(ifunc)机制。该机制是GNU工具链的一个特性,它允许开发者创建给定函数的多个实现,在运行时使用一个解析器函数在其中选择。在启动早期这个解析器函数由动态载入器调用,决定应用程序使用哪个实现。一旦做出了实现选择,就固定下来,在这个过程的生命期里不变了。

gcc 6中,解析器检查CPUID并调用相应的函数。它对每个二进制执行文件都做一次。因此当存在对FMV函数的多个调用时,仅第一个调用会执行CPUID比较;后续调用将通过一个指针找到要求的版本。这个技术已经用于几乎所有的glibc函数。例如,glibc对每个架构都优化了memcpy(),因此当调用时,glibc将调用恰当优化的memcpy()

代码大小影响

FMV将增加二进制代码的大小,但这个影响可以最小化。代码大小的增加依赖于应用FMV的函数有多大,以及要求版本的数量。如果最初二进制代码大小是CN是请求的版本数(包括缺省),R是这些函数占整个应用程序代码的比例,新代码的大小将是:

    (1 - R) * C + R * C * N

如果一个应用程序最热代码占总大小的1%,且应用FMV支持三个架构(缺省,sse4.2avx2),代码大小总共增加2%。在考虑今天的储存容量时,这是相当小的影响。但这种影响必须基于部署模型来考虑。性能、维护性与增加的二进制代码间存在权衡,因此对某种类型的部署FMV可能不是正确的选择(比如物联网设备)。

结果

下表展示了在不同处理器上,使用不同gcc标记运行array_addition.c的执行时间:

执行时间(ms

GCC标记

Haswell

Skylake

Broadwell

Xeon

Atom

Ivy Bridge

None

603

645

580

1413

2369

517

-O3

38

44

37

107

96

60

-O3 -mavx

26

32

26

73

SIGILL

45

-O3 -mavx2

26

32

26

73

SIGILL

SIGILL

-O3 (with FMV)

26

32

26

73

96

45

FMV版本使用下面的指示:

   __attribute__((target_clones("avx2","arch=atom","default")))

SIGILL项表示对某些组合是非法指令。缺省的CFLAGS(不是特别值得注意),配置作为Clear Linux for Intel Architecture项目部分说明。

实例

今天,越来越多行业部门从基于云的科学计算中获益。这些部门包括化工、财务以及分析应用程序。其中一个更受欢迎的科学计算库是用于PythonNumPy库。它包括了对大的、多维数组与矩阵的支持。它还有用于线性代数、傅里叶变换以及随机数生成等等的特性。

在一个诸如NumPy的科学库里使用FMV技术的好处通常是它得到良好的理解与接受。如果没有启用向量化,SIMD寄存器里许多未用的空间浪费了。如果启用向量化,在一条指令里编译器使用额外的寄存器执行更多的操作(比如我们例子里更多整数加法)。

由于FMV技术性能的提升(运行在带有AVX2指令的Haswell机器上),对科学计算内容可以到达3%。我们使用运行在1.8GHzSkylake系统上的OpenBenchmarking.org numpy-1.0.2,使用FMV运行时间是8400秒,而在使用-O3编译时是8600秒。

性能提升归功于从向量化受益的NumPy代码里的函数。为了检测这些函数,gcc提供了标记-fopt-info-vec。这个标记用于检测向量化候选函数。例如,以这个标记构建NumPy将告诉我们文件fftpack.c有可以使用向量化的代码:

    numpy/fft/fftpack.c:813:7: note: loop peeled for vectorization to enhance alignment

查看NumPy源代码显示radfg()函数,这是NumPy里支持的快速傅里叶变换的一部分,执行大量可以使用AVX优化的数组加法。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需要多个目标机器可用,因此现在使用容器SubtargetMap(类型mutable StringMap<std:: unique_ptr<X86Subtarget>>)来保存多个X86Subtarget实例,键值是描述目标CPU以及各方面特性的字符串,这个字符串确保唯一。

317行的resetTargetOptions()根据当前函数的属性改写由InitTargetOptionsFromCodeGenFlags()等根据编译命令行设置的属性。

在318行创建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的构造函数是TableGen生成的,前面我们已经看到,它将MC层的一组指针指向X86目标机器特定的参数。300行的成员TSInfo的类型是X86SelectionDAGInfo,目标机器通过它可以提供对memcpy、memmove、memset、memcmp、memchr、strcpy、strcmp、strlen,这些操作的专属处理代码(v7.0删除这个调用)。

303行的isPositionIndependent()检查使用的重定位模型是否为Reloc::PIC_,这些重定位模型用于动态库的生成。V7.0简化为这几种:StubPICi386-darwinpic),GOT(全局对象表,32elfpic),RIPRel(相对RIP64elfpic),None(没有使用pic)。位置无关代码参考有关资料(如《C++高级编译》)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/135508.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pytest 的使用===谨记

发现用例的规则 a) 文件test_.py开头和_test.py结尾 b) Test开头的类中test开头的方法&#xff08;测试类不能带有__init__方法&#xff09; c) 模块中test开头的函数&#xff08;可以不在class中&#xff09; 注意点&#xff1a; pytest是以方法为单位发现用例的&#xff0c;你…

@Bean有哪些属性

直接看原文 原文链接:Spring中bean标签的作用和属性的详解-CSDN博客 -------------------------------------------------------------------------------------------------------------------------------- Bean的配置一般都在XML文件进行配置Bean相关包为&#xff1a;or…

吴恩达《机器学习》6-4->6-7:代价函数、简化代价函数与梯度下降、高级优化、多元分类:一对多

一、代价函数 逻辑回归的代价函数是用来度量模型预测与实际结果之间的差异的。与线性回归使用均方误差不同&#xff0c;逻辑回归使用的代价函数在数学上更为复杂。为了理解逻辑回归的代价函数&#xff0c;首先要明白逻辑回归的假设函数&#xff1a; ℎ&#x1d703;(&#x1…

激光雷达和人工智能

几十年来&#xff0c;激光雷达一直是许多行业中非常有用的工具&#xff0c;但直到最近&#xff0c;随着人工智能&#xff08;AI&#xff09;解决方案的引入&#xff0c;我们才开始认识到它的真正潜力。激光雷达&#xff0c;又称光探测和测距&#xff0c;是一种遥感技术。它利用…

React构建组件的方式有哪些?区别?

一、是什么 组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念&#xff08;组件&#xff09;来实现开发的模式 在React中&#xff0c;一个类、一个函数都可以视为一个组件 在之前文章中&#xff0c;我们了解到组件所存在的优势&#xff1a; 降低整个系统的耦合度&am…

nginx下载安装和日志切割

目录 一、nginx安装配置 1.nginx版本 2.nginx安装配置 3.查看安装后的nginx 4.配置PATH变量 二、日志切割 1.给当前日志文件重命名 2.等待 3.写bash脚本 4.查看日志结果 5.加入crontab定时任务 结语 一、nginx安装配置 1.nginx版本 nginx如今分为商业版&#xff0…

imagettftext(): Could not find/open font 解决办法

问题&#xff1a;Captcha验证码不能正常显示&#xff0c;是因为使用GD库imagettftext()函数时&#xff0c;报“Warning: imagettftext(): Could not find/open font in ”警告 。 网上的解决方法: 将font路径的相对路径 转成 绝对路径即可 $fontfile "./fonts/*.ttf&q…

MaHDE

FHM means ‘fitness hierarchical mutation’&#xff0c;DGS means ‘directed global search’&#xff0c;ELS means ‘elite local search’ 辅助信息 作者未提供代码

使用数据分析,识别设备异常

设备健康监测系统在工业领域中扮演着至关重要的角色&#xff0c;它能够帮助企业及时发现设备异常&#xff0c;预防故障&#xff0c;提高设备使用寿命和生产效率。而异常诊断技术则是设备健康监测系统中的核心部分&#xff0c;能够实现对设备异常情况的准确判断。根据设备状态数…

高校为什么需要企业数据库?

随着信息化数字化的发展&#xff0c;企业数据库已经成为高校不可或缺的一部分。企业数据库一般整合了多维度企业数据信息。比如&#xff0c;艾思依托丰富的数据沉淀和领先的模型算法&#xff0c;打造“1N”产业大数据平台&#xff0c;包含“1个企业数据中心”一一涵盖全国2.4亿…

【uniapp】解决在H5谷歌浏览器下 u-input 标签 设置只读后,click事件不生效

【问题描述】 谷歌浏览器更新后&#xff0c;h5模式下原本的input外层view中的click事件不触发了?? 但是更换浏览器后就可以&#xff0c;打包app也是正常可以触发的&#xff0c;本来是没打算兼容h5&#xff0c;既然遇到了就记录一下~ 【解决办法】 使u–input里写上readonly&…

【python海洋专题四十】海洋指数画法--单色填充图

【python海洋专题四十】海洋指数画法–单色填充图 【python海洋专题四十】海洋指数画法–单色填充图 数据:AMO_index 图像展示: 图片 往期推荐 图片 【python海洋专题一】查看数据nc文件的属性并输出属性到txt文件 【python海洋专题二】读取水深nc文件并水深地形图 【p…

[PHP]得推跑腿O2O系统 v3.41

得推跑腿系统是一个以phpMySQL进行开发的主要针对本地跑腿服务的O2O系统&#xff0c;支持wap\\小程序\\App。 主要功能模块&#xff1a; 用户端&#xff1a; 1.跑腿任务发布 2.跑腿任务管理追踪 3.在线支付 4.常用地址管理 跑腿端&#xff1a; 1.跑腿任务抢单 2.跑腿员认证 3.…

2023年Q3乳品行业数据分析(乳品市场未来发展趋势)

随着人们生活水平的不断提高以及对健康生活的追求不断增强&#xff0c;牛奶作为优质蛋白和钙的补充品&#xff0c;市场需求逐年增加。 今年Q3&#xff0c;牛奶乳品市场仍呈增长趋势。根据鲸参谋电商数据分析平台的相关数据显示&#xff0c;2023年7月-9月&#xff0c;牛奶乳品市…

【星海随笔】git的使用

1.在终端&#xff0c;检查git是否安装 git --version 2.没有安装的话去&#xff0c;官网&#xff0c;下载git 3.一直点下一步即可 4.安装后在终端检查git是否安装好 5.设置用户名和邮件地址(最好和GitHub的用户名/邮箱保持一致) git config --global user.name “自己的用户名”…

数据结构: 哈希桶

目录 1.概念 2.模拟实现 2.1框架 2.2哈希桶结构 2.3相关功能 Modify --Insert --Erase --Find 2.4非整型数据入哈希桶 1.仿函数 2.BKDR哈希 1.概念 具有相同地址的key值归于同一集合中,这个集合称为一个桶,各个桶的元素通过单链表链接 2.模拟实现 2.1框架 a.写出…

2.3.3 交换机的RSTP技术

实验2.3.3 交换机的RSTP技术 一、任务描述二、任务分析三、具体要求四、实验拓扑五、任务实施1.交换机的基本配置。2.开启交换机的STP。3.配置SW3A和SW3B上STP的优先级。将SW3A配置为根交换机&#xff0c;SW3B配置为备用根交换机。4.配置SW2A和SW2B的边缘接口 六、任务验收七、…

使用基于swagger的knife4j自动生成接口文档

添加swagger依赖springfox&#xff0c;添加knife4j依赖 <dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId> </dependency> <dependency><groupId>com.github.xiaoymin</group…

C语言:union类型

C语言的union类型是一种用于存储不同数据类型的数据结构&#xff0c;但与struct类型不同&#xff0c;union只能同时存储其中的一个成员的值&#xff0c;而不是所有成员的值。这可以节省内存&#xff0c;因为union的大小等于其最大成员的大小。 union UnionName {data_type mem…

postman中文乱码

在header中添加这两个&#xff1a; Content-Type application/json;charsetUTF-8 Accept application/json;charsetUTF-8