这是我参与「第五届青训营 」笔记创作活动的第九天
一、本堂课重点内容:
- 编译器和静态分析
- Go编译器优化
二、详细知识点介绍:
编译器的结构
编译器是一个很重要的系统软件,它能够识别到不符合要求的代码拼写,而且可以帮助生成正确的代码
它主要有两个部分组成:
首先,是分析部分(前端
front end):
- 词法分析,生成语速(
lexeme)- 语法分析,生成语法树
- 语义分析,收集类型信息,进行语义检查
- 中间代码生成,生成
intermediate representation(IR)然后是综合部分(后端
back end)
- 代码优化,机器无关优化,生成优化后的IR
- 代码生成,生成目标代码
我们主要学习编译器后端优化
静态分析
接下来我们来了解有关静态分析的概念
静态分析指的是:不执行程序代码,推导程序的行为,分析程序的性质
然后我们可以通过分析控制流(程序执行的流程
Control flow)和数据流(数据在控制流上的传递
Data flow)从而得到更多关于程序的性质
我们就可以通过这些性质来优化代码
过程内和过程间分析
接下来我们来了解一下有关过程内和过程间分析的概念:
- 过程内分析(
Intra-procedural analysis):就是仅在函数内部进行分析- 过程间分析(
Inter-procedural analysis):考虑过程调用时参数传递和返回值的数据流和控制流那为啥过程间分析是一个问题呢?我们以下面这个例子作解释:
我们如果想要分析这个
i.foo()的话我们首先得需要通过数据流分析得知i得具体类型,然后才能知道
i.foo()调用的是哪个foo()之后我们根据i的具体类型,产生了新的控制流,
A.foo(),分析继续······由上面的例子不难看出——过程间分析需要同时分析控制流和数据流,所以就会联合求解,比较复杂
Go编译器优化
首先我们得了解为什么我们要做编译器优化,好处在哪:
- 用户无感知,重新编译即可获得性能收益
- 通用性优化
目前对于编译器优化的现状:
- 采用的优化少
- 编译时间较短,没有进行较复杂的代码分析和优化
对于编译器优化面对的场景:面对后端长期执行任务
优化思路:用编译时间换取更高效的机器码
比较好的优化模式:
- 函数内联
- 逃逸分析
- 默认栈大小调整
- 边界检查消除
- 循环展开
- ······
接下来,我们来了解一下函数内联和逃逸分析
函数内联(InIning):
函数内联就是将被调用函数的函数体(
callee)的副本替换到调用的位置(caller)上,同时重写代码以反映参数的绑定。优点:
- 消除函数调用的开销,例如传递参数、保存寄存器等
- 将过程间分析转化为过程内分析,帮助其他优化,例如逃逸分析
缺点:
- 函数体变大,
instruction cache(icache)不友好- 编译生成的Go镜像变大
内联策略:
因为Go函数中内联受到的限制有很多(语言特性,例如
interface,defer等,限制了函数的内联)所以我们要及时调整内联策略,使更多的函数被内联
- 降低函数调用的开销
- 增加其他优化的机会:逃逸分析
开销:
- Go镜像增加~10%
- 编译时间增加
逃逸分析
逃逸分析是指分析代码中指针的 动态作用域(指针在何处可以被访问)
大致思路:
- 从对象分配处出发,沿着控制流,观察对象的数据流
- 若发现指针p在当前作用域s:
- 作为参数传递给其他函数
- 传递给全局变量
- 传递给其他的goroutine
- 传递给已逃逸的指针指向的对象
- 则指针p指向的对象逃逸出s,反之则为没有
优化:为逃逸i的对象可以在栈上分配
- 对象在栈上分配和回收很快:移动sp
- 减少在heap上的分配,降低GC负担
三、课后个人总结:
这次学习主要了解了有关编译器结构与优化的一些知识
在这一次学习中,我了解到编译器的具体内部结构、静态分析、过程内和过程间的分析等等的一些概念
也学到了函数内联、逃逸分析等多种编译器优化思路
为以后在开发过程中面对此类问题时,提供了思路