小米技术出品

您所在的位置:网站首页 command指令集 小米技术出品

小米技术出品

#小米技术出品| 来源: 网络整理| 查看: 265

本文来自小米信息技术团队,作者为小米信息技术部海外商城组何磊   1. 认识 go build go build

这个命令会编译 go 代码,今天就来一起看看 go 的编译过程吧!

首先先来认识以下 go 的代码源文件分类

_test.gogo build

640?wx_fmt=png

接下来就用一个 hello world 程序来演示以下上面的命令选项。

640?wx_fmt=png

go build -n

640?wx_fmt=png

来分析下整个执行过程

640?wx_fmt=png

compilebuildidlinka.out mv compilebuildid、link 2. 编译器原理

这是 go 编译器的源码路径

640?wx_fmt=png

如上图所见,整个编译器可以分为:编译前端与编译后端;现在我们看看每个阶段编译器都做了些什么事情。先来从前端部分开始。

2.1 词法分析 Token GolangToken

640?wx_fmt=png

640?wx_fmt=png

Token

首先先来给 Go 的 token 类型分个类:变量名、字面量、操作符、分隔符以及关键字。我们需要把一堆源代码按照规则进行拆分,其实就是分词,看着上面的例子代码我们可以大概制定一个规则如下:

()TokenGolanglex 2.2 语法分析 TokenAST TokenAST TokenSTRINGfuncASTToken

640?wx_fmt=png

ASTToken

这颗树构造后,我们可以看到不同的类型是由对应的结构体来进行表示的。这里如果有语法、词法错误是不会被解析出来的。因为到目前为止说白了都是进行的字符串处理。

2.3 语义分析

编译器里边都把语法分析后的阶段叫做 语义分析,而 go 的这个阶段叫 类型检查;但是我看了一下 go 自己的文档,其实做的事情没有太大差别,我们还是按照主流规范来写这个过程。

那么语义分析(类型检查)究竟要做些什么呢?

AST Golang

如下面的文字所说:

The AST is then type-checked. The first steps are name resolution and type inference, which determine which object belongs to which identifier, and what type each expression has. Type-checking includes certain extra checks, such as “declared and not used” as well as determining whether or not a function terminates.

大意是:生成 AST 之后是类型检查(也就是我们这里说的语义分析),第一步是进行名称检查和类型推断,确定每个对象所属的标识符,以及每个表达式具有什么类型。类型检查也还有一些其它的检查要做,像“声明未使用”以及确定函数是否中止。

Certain transformations are also done on the AST. Some nodes are refined based on type information, such as string additions being split from the arithmetic addition node type. Some other examples are dead code elimination, function call inlining, and escape analysis.

这一段是说:AST 也会进行转换,有些节点根据类型信息进行精简,比如从算术加法节点类型中拆分出字符串加法。其它一些例子像 dead code 的消除,函数调用内联和逃逸分析。

上面两段文字来自 golang compile。

这里多说一句,我们常常在 debug 代码的时候,需要禁止内联,其实就是操作的这个阶段。

640?wx_fmt=png

经过语义分析之后,就可以说明我们的代码结构、语法都是没有问题的。所以编译器前端主要就是解析出编译器后端可以处理的正确的 AST 结构。

接下来我们看看编译器后端又有哪些事情要做。机器只能够理解二进制并运行,所以编译器后端的任务简单来说就是怎么把 AST 翻译成机器码。

2.4 中间码生成

既然已经拿到 AST,机器运行需要的又是二进制。为什么不直接翻译成二进制呢?其实到目前为止从技术上来说已经完全没有问题了。

makeslice64makeslice

中间码存在的另外一个价值是提升后端编译的重用,比如我们定义好了一套中间码应该是长什么样子,那么后端机器码生成就是相对固定的。每一种语言只需要完成自己的编译器前端工作即可。这也是大家可以看到现在开发一门新语言速度比较快的原因。编译是绝大部分都可以重复使用的。

而且为了接下来的优化工作,中间代码存在具有非凡的意义。因为有那么多的平台,如果有中间码我们可以把一些共性的优化都放到这里。

Golang 2.5 代码优化

在 go 的编译文档中,我并没找到独立的一步进行代码的优化。不过根据我们上面的分析,可以看到其实代码优化过程遍布编译器的每一个阶段。大家都会力所能及的做些事情。

通常我们除了用高效代码替换低效的之外,还有如下的一些处理:

并行性,充分利用现在多核计算机的特性

流水线,cpu 有时候在处理 a 指令的时候,还能同时处理 b 指令

指令的选择,为了让 cpu 完成某些操作,需要使用指令,但是不同的指令效率有非常大的差别,这里会进行指令优化

利用寄存器与高速缓存,我们都知道 cpu 从寄存器取是最快的,从高速缓存取次之。这里会进行充分的利用

2.6 机器码生成

经过优化后的中间代码,首先会在这个阶段被转化为汇编代码(Plan9),而汇编语言仅仅是机器码的文本表示,机器还不能真的去执行它。所以这个阶段会调用汇编器,汇编器会根据我们在执行编译时设置的架构,调用对应代码来生成目标机器码。

GolangGOARCH=xxx cmd/compile/main.go:main()

640?wx_fmt=png

cmd/internal/obj/plist.go

640?wx_fmt=png

整个过程下来,可以看到编译器后端有很多工作需要做的,你需要对某一个指令集、cpu 的架构了解,才能正确的进行翻译机器码。同时不能仅仅是正确,一个语言的效率是高还是低,也在很大程度上取决于编译器后端的优化。特别是即将进入 AI 时代,越来越多的芯片厂商诞生,我估计以后对这方面人才的需求会变得越来越旺盛。

3. 总结

总结一下学习编译器这部分古老知识带给我的几个收获:

Golang

本文的很多信息都来自下面的资料。

[1]golang compile

[2]golang ssa

[3]golang command

[4]golang compile 介绍

[5]golang 编译流程分析



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3