0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

编译器可以为你生成高性能的代码,但是你真的需要编译器吗?

电子工程师 来源:lp 2019-03-11 09:35 次阅读

编译的目的是将源码转化为机器可识别的可执行程序,在早期,每次编译都需要重新构建所有东西,后来人们意识到可以让编译器自动完成一些工作,从而提升编译效率。

但“编译器不过是用于代码生成的软机器,你可以使用你想要的任何语言来生成代码”,真的是必要的吗?

诚然,编译器可以为你生成高性能的代码,但是你真的需要编译器吗?另一种方法是用 Assembly 编写程序,虽然有点夸大,但这种方法有两个主要缺陷:

1. 汇编代码不可移植;

2. 虽然在现代工具的辅助下变得容易了些,但 Assembly 编程仍然需要大量繁琐的工作。

值得庆幸的是,我们都生活在二十一世纪,这两个问题都已得到解决。第一个解决方案是LLVM,最初,它意味着“低级虚拟机”,这正是我们可以确保可移植性的原因。简而言之,它需要用一些非常低级别的与硬件无关语言编写的代码,并为特定的硬件平台返回一些高度优化的原生代码。使用 LLVM,我们既具有低级编程的强大功能,又具有面向硬件微优化的自动化功能。

第二个问题的解决方法是使用“脚本”语言,Scheme、Python、Perl,甚至 bash 或 AWK 都可以消除繁琐的工作。

实验计划

首先,让我们生成一个完全内联展开的解决方案,并将其嵌入到基准测试代码中。该计划如下:

1. 使用 Clang 为基准生成 LLVM 中间代码,该基准用于测量 solve_5,一个不存在的函数;

2. 使 Python 在 LLVM 中生成线性求解器(linear solver)代码;

3. 使用 Python 脚本测试基准,用生成求解器替换 solve_5 调用;

4. 使用 LLVM 静态编译器将中间代码转换为机器代码;

5. 使用 GNU 汇编器和 Clang 的链接器将机器代码转换为可执行的二进制文件。

这就是它在 Makefile 中的样子:

Python 部分

我们需要 Python 中的线性求解器(linear solver),就像我们使用 C 和 C ++ 一样,此处代码为:

#thisgeneratesn-solverinLLVMcodewithLLVMCodeobjects.#NoLLVMstuffyet,justcompletelyPythonicsolutiondefsolve_linear_system(a_array,b_array,x_array,n_value):defa(i,j,n):ifn==n_value:returna_array[i*n_value+j]returna(i,j,n+1)*a(n,n,n+1)-a(i,n,n+1)*a(n,j,n+1)defb(i,n):ifn==n_value:returnb_array[i]returna(n,n,n+1)*b(i,n+1)-a(i,n,n+1)*b(n,n+1)defx(i):d=b(i,i+1)forjinrange(i):d-=a(i,j,i+1)*x_array[j]returnd/a(i,i,i+1)forkinrange(n_value):x_array[k]=x(k)returnx_array

当我们用数字运行时,我们可以得到数字。但我们想要代码,因此,我们需要制作一个假装成数字的对象(Object)来探测算法。该对象记录下算法想要执行的每一个操作,并准备好集成 LLVM 中间语言。

#thisisbasicallythewholeLLVMlayerI=0STACK=[]classLLVMCode:#theonlyconstructorfornowisbydouble*instructiondef__init__(self,io,code=''):self.io=ioself.code=codedef__getitem__(self,i):globalI,STACKcopy_code="%"+str(I+1)copy_code+="=getelementptrinboundsdouble,double*"copy_code+=self.io+",i64"+str(i)+" "copy_code+="%"+str(I+2)copy_code+="=loaddouble,double*%"+str(I+1)copy_code+=",align8 "I+=2STACK+=[I]returnLLVMCode(self.io,copy_code)def__setitem__(self,i,other_llvcode):globalI,STACKself.code+=other_llvcode.codeself.code+="%"+str(I+1)self.code+="=getelementptrinboundsdouble,double*"self.code+=self.io+",i64"+str(i)+" "self.code+="storedouble%"+str(I)self.code+=",double*%"+str(I+1)+",align8 "I+=1STACK=STACK[:-1]returnselfdefgeneral_arithmetics(self,operator,other_llvcode):globalI,STACKself.code+=other_llvcode.code;self.code+="%"+str(I+1)+"=f"+operatorself.code+="double%"+str(STACK[-2])+",%"self.code+=str(STACK[-1])+" ";I+=1STACK=STACK[:-2]+[I]returnselfdef__add__(self,other_llvcode):returnself.general_arithmetics('add',other_llvcode)def__sub__(self,other_llvcode):returnself.general_arithmetics('sub',other_llvcode)def__mul__(self,other_llvcode):returnself.general_arithmetics('mul',other_llvcode)def__div__(self,other_llvcode):returnself.general_arithmetics('div',other_llvcode)

接着,当我们使用这种对象运行求解器时,我们得到了一个用 LLVM 中间语言编写的全功能线性求解器。然后我们将其放入基准代码中进行速度测试(看它有多快)。

LLVM 中的指令有编号,我们希望保存枚举,因此将代码插入到基准测试中的函数很重要,但也不是很复杂。

#thisreplacesthefunctioncall#andupdatesalltheinstructions'indicesdefreplace_call(text,line,params):globalI,STACK#'%12'->12I=int(''.join([xiforxiinparams[2]ifxi.isdigit()]))first_instruction_to_replace=I+1STACK=[]replacement=solve_linear_system(LLVMCode(params[0]),LLVMCode(params[1]),LLVMCode(params[2]),5).codedelta_instruction=I-first_instruction_to_replace+1foriinxrange(first_instruction_to_replace,sys.maxint):not_found=sum([text.find('%'+str(i)+c)==-1forcinPOSSIBLE_CHARS_NUMBER_FOLLOWS_WITH])ifnot_found==4:#thelastinstructionhasalreadybeensubstitutedbreaknew_i=i+delta_instructionforcinPOSSIBLE_CHARS_NUMBER_FOLLOWS_WITH:#substituteinstructionnumbertext=text.replace('%'+str(i)+c,'%'+str(new_i)+c)returntext.replace(line,replacement)

实现解算器的整段代码提供了 Python-to-LLVM 层,其中代码插入只有 100 行!

另附 GitHub 链接:

https://github.com/akalenuk/wordsandbuttons/blob/master/exp/python_to_llvm/exp_embed_on_call/substitute_solver_call.py

基准

基准测试本身在 C 中。当我们运行 Makefile 时,它对 solve_5 的调用被 Python 生成的 LLVM 代码所取代。

Step 1. Benchmark C source code

Step 2. LLVM 汇编语言

Step 3. 调用替换后的 LLVM

Step 4. 本地优化装配

最值得注意的是 Python 脚本生成的超冗长中间代码如何变成一些非常紧凑且非常有效的硬件代码。同时它也是高度标量化的,但它是否足以与 C 和 C++ 的解决方案竞争呢?

以下是三种情况的近似数字(带有技巧的 C、C++ 与基于 LLVM 的 Python 的性能对比):

1. C 的技巧对 Clang 来说并不适用,因此测量 GCC 版本,其平均运行大约 70 毫秒;

2. C++ 版本是用 Clang 构建的,运行时间为 60 毫秒;

3. Python 版本(此处描述的版本)仅运行 55 毫秒。

当然,这种加速并不是关键,但这表明你可以用 Python 编写出胜过用 C 或 C++ 编写的程序。这也就暗示你不必学习一些特殊语言来创建高性能的应用程序或库。

结论

快速编译语言和慢速脚本语言之间的对立不过是虚张声势。原生代码生成的可能不是核心功能,而是类似于可插拔选项。像是Python 编译器Numba或Lua 的Terra,其优势就在于你可以用一种语言进行研究和快速原型设计,然后使用相同的语言生成高性能的代码。

高性能计算没有理由保留编译语言的特权,编译器只是用于代码生成的软机器。你可以使用你想要的任何语言生成代码,我相信如果你愿意,你可以教 Matlab 生成超快的 LLVM 代码。

本文涉及的所有测试均在 Intel(R)Core(TM)i7-7700HQ CPU @ 2.80GHz 上进行,代码使用 Clang 3.8.0-2ubuntu4 和 g++5.4.0 编译。

基准测试源代码:

https://github.com/akalenuk/wordsandbuttons/tree/master/exp/python_to_llvm


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 代码
    +关注

    关注

    30

    文章

    4553

    浏览量

    66665
  • 编译器
    +关注

    关注

    1

    文章

    1570

    浏览量

    48604
  • python
    +关注

    关注

    51

    文章

    4667

    浏览量

    83443

原文标题:都有Python了,还要什么编译器!

文章出处:【微信号:rgznai100,微信公众号:rgznai100】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    如何编写有利于编译器优化的代码

    对于嵌入式系统,最终代码的体积和效率取决于由编译器生成的可执行代码,而非开发人员编写的源代码但是
    发表于 11-09 10:31 1289次阅读
    如何编写有利于<b class='flag-5'>编译器</b>优化的<b class='flag-5'>代码</b>

    Loop Interchange如何帮助编译器生成更高效的可执行代码

    C/C++代码编译时,编译器将源码翻译成CPU可识别的指令序列并生成可执行代码,而最终代码的运
    发表于 08-03 10:56 347次阅读

    交叉编译器解析

    使用针对该平台的编译器,来重新编译该应用程序的二进制代码,就像我们现在在身边进程遇到的,如果使用的是苹果手机,下载需要的app,会提醒
    发表于 12-16 09:47

    交叉编译器解析

    使用针对该平台的编译器,来重新编译该应用程序的二进制代码,就像我们现在在身边进程遇到的,如果使用的是苹果手机,下载需要的app,会提醒
    发表于 12-21 16:57

    PSV有什么问题?是编译器错误吗?

    你好,我想问一下的经历。我有这个代码编译器如何编译它是有趣的。同一类型有两个变量TASK1和TASK2。当我将&ela1Tasks[0]分配给task1时,
    发表于 05-05 07:13

    关于编译器生成的map和htm文件看完就懂了

    关于编译器生成的map和htm文件看完就懂了
    发表于 10-12 13:01

    C编译器的设计文档与源代码

    C-编译器的设计文档与源代码:本压缩包包含了C-编译器的设计文档与源代码,供学习参考。  整体框架. 3 词法分析. 3᠙
    发表于 02-09 11:13 45次下载

    基于CoSy的编译器开发的研究

    CoSy是ACE公司开发的编译器构造框架[1]。它提供共享工具和引擎来构造编译器编译器开发者只专注于目标机相关代码的开发。CoSy框架生成
    发表于 08-19 17:49 0次下载
    基于CoSy的<b class='flag-5'>编译器</b>开发的研究

    编译器是如何工作的_编译器的工作过程详解

    随着计算机的发展,编译器已经发挥着十分重要的作用。本文主要介绍了编译器的种类、编译器的工作原理以及编译器工作的具体操作过程及步骤详解。
    发表于 12-19 12:54 1.5w次阅读

    MPLAB® XC8 C编译器的架构特性

    本视频介绍了MPLAB® XC8 C编译器的架构特性。该编译器编译过程不同于传统的编译器,采用了一种称为"OCG(全知代码
    的头像 发表于 05-23 12:47 5428次阅读
    MPLAB® XC8 C<b class='flag-5'>编译器</b>的架构特性

    王垠谈编译器

    由于早期的 Lisp 编译器生成代码效率普遍低下,成为了 Lisp 失败的主要原因之一。而现在的高性能 Lisp 编译器(比
    的头像 发表于 03-30 10:45 1860次阅读

    CompCert编译器目标代码生成机制研究综述

    对 Compcert编译器目标代码生成机制进行剖析,主要介绍其设计逻辑、翻译过程、语义保持性以及代码结构,并给出了 Compcert编译器
    发表于 05-07 10:17 4次下载

    如何编写有利于编译器优化的代码

    对于嵌入式系统,最终代码的体积和效率取决于由编译器生成的可执行代码,而非开发人员编写的源代码但是
    的头像 发表于 03-29 15:58 1139次阅读
    如何编写有利于<b class='flag-5'>编译器</b>优化的<b class='flag-5'>代码</b>

    交叉编译器安装教程

    ,所以我们需要一个在 X86 架构的 PC 上运行,可以编译 ARM 架构代码的 GCC 编译器,这个
    的头像 发表于 09-29 09:12 2621次阅读

    编译器的优化选项

    这一点,需要了解编译器的能力和限制;第三,要了解硬件的运行方式,针对硬件特性进行优化。本文着重展开第二点和第三点。 简单认识编译器 要写出高性能
    的头像 发表于 11-24 15:37 394次阅读
    <b class='flag-5'>编译器</b>的优化选项