多年前编程圈流行一个段子:“以代码重用为荣,以复制粘贴为耻”,这个段子应该加一个限制:“仅限于后端”,就算后端也不能100%的执行这个原则,否则可能会把自己框死。

LLVM IR 简介

按照 LLVM 官网 的解释:

The LLVM Project is a collection of modular and reusable compiler and
toolchain technologies. Despite its name, LLVM has little to do with
traditional virtual machines, though it does provide helpful libraries
that can be used to build them. The name “LLVM” itself is not an
acronym; it is the full name of the project.

澳门新葡亰8455下载app,发展到现在 LLVM 已经不仅仅是一个编译器项目了,不过为了介绍
IR,我们不妨从编译器的角度来介绍一下
LLVM。编译器分为前端和后端,前端主要包括语法解析、语义分析以及生成抽象语法树,而后端则包括了汇编代码、目标文件的生成以及连接等等过程。LLVM
的目标是使前端和后端高度模块化,高度的每个模块都可重用,具体的做法就是通过
LLVM IR 连接前端和后端

LLVM IR 可以抽象的理解为 java 的字节码,后端理解成 jvm
的虚拟机,这样编译器开发人员就可以很方便的为某种语言开发一套编译器了:他只需要实现一个前端,用
LLVM IR
表示抽象语法树即可。而要添加对某个处理器平台的支持只需要添加对应平台的后端支持即可

前端,过度的注重重用,会让代码变得很抽象,用抽象的代码实现直观的界面,就像用筷子夹鸡蛋,何必跟自己过不去。前端该复制时就复制,该粘贴时就粘贴,做前端,产品思维比技术思维更重要

为什么使用 LLVM IR

让我们来看一段伪代码:

for (int i = 0; i < num_fields_; ++ i) {
    switch (type_[i]) {
        case INTEGER:
            ProcessIntegerField(field_[i]);
            break;
        case STRING:
            ProcessStringField(field_[i]);
            break;
        ......
    }
}

类似这样的代码我们经常遇见,它有两个性能热点:1. for
循环的条件判断:i < num_field_s;2. 循环体中的 switch case 表达式

而在运行时,num_fields_ 以及 type_[i] 的值都是确定的,更准确的说:在执行上面那段代码钱,这两个值都是确定的,因此相比于上面的代码,下面的代码更加高效:

ProcessIntegerField(field_[0]);
ProcessStringField(field_[1]);
ProcessStringField(field_[2]);
ProcessDoubleField(field_[3]);

上面的代码中,我们假设 num_fields_ 的值为 4,type_[0] 到 type_[3] 的值分别为 INTEGER、STRING、STRING 以及 DOUBLE。这样的代码没有了分支判断,极大的增加了程序的执行性能

上面的例子告诉我们,在程序运行时,根据那些在函数运行前能确定的值,我们可以通过生成一个更加高效的、实现同样功能的函数来提高程序的执行效率,尤其是当那个函数被反复调用多次的时候,优化效果尤为明显

因为生成函数需要时间,实际上我们不一定非要选择 IR,完全可以选择其他语言,比如 C/Java/Python/PHP 等等。从生成函数到函数被编译成机器码也是一个时间开销,这个时间越短越好,而生成 IR 再利用 llvm 的 JIT 技术,这一时间被控制的非常非常短(随 IR 的大小而变化),因此 LLVM IR 成了不二的选择:利用 LLVM 提供的函数库在程序运行时根据已知的变量信息动态生成函数的 LLVM IR 代码,接下来利用 LLVM 的 JIT 库把 IR 代码转化为对应的机器码,并返回函数的指针,最后程序拿到函数指针调用生成的函数进行计算

网站地图xml地图