正文
目录
讲汇编优化,不得不说一句高德纳的名言——过早的优化就是万恶之源。如果你们没有被逼到绝路,或者要榨干CPU的性能,千万不要尝试以下演讲的内容。
我给 Go 的 1.11 提交了这几个项目,第一个是 Hashmap 优化,就是你们常用的 map 操作里面最费时的哈希值计算优化。VDSO,虚拟动态对接的 syscall,主要是优化系统时间调用。Md5、Chacha20就不说了。还有一个 Duffcopy,这是给编译器展开优化用的,它在 arm64平台优化得不是很好,所以我也做了优化。除了 Chacha20还没有完成外,其他的都已经在 Go master 上可以用到了。可能有些人会觉得为什么都是 arm64
平台的优化?其实就是 Go 官方团队维护了 X86-64,已经优化得很好,我就不要搀和了,就挑了一个比较新的平台,arm64。
国内 arm 公司的大牛肖玮带领他的团队也在做 Go 相关的优化,比如 sha256,提升的效率有 16倍。国外的也有,Cloudflare,做CDN的公司,他们有一个密码学大牛弗拉德做了一些优化,也在 Go 的1.11里面合进去了,优化的效率是多少呢?
这是他们的CTO转发的推,CTO问他上周优化了一些什么东西呢?他说他优化了一些Go的库,RSA 性能有20倍,AES-GCM有15倍,P256有18倍。看了这些大牛优化以后有这么好的性能提升,是不是很心动啊?这次演讲就是教大家入门汇编优化,怎么做十几倍的加速。
1. 基础知识
所以怎么跑得那么快?就要知道干什么。 总结下来有三点,减少读写,并行操作,硬件加速。
1.1
减少读写
上图是谷歌的 Jeff Dean 分享的《程序员应该知道的延迟》,这个延迟是什么延迟呢?比如数据从CPU L1里面挖出来的速度,在2012年的时候是0.5ns,CPU L2里面是7ns;储存,也就是我们常说的内存里面拉出来是100ns。大家有没有发现每多一层就是10倍的性能下降,所以你要尽量少用内存的操作,多用寄存器。还有,CPU访问内存的时候有一个小窍门,把这个对齐再访问,CPU会执行得更快一些。这些都是基本知识,大家可以百度、Google 一下,不展开。
1.2
并行操作
业内叫做并行操作SIMD,就是单一指令多个数据进行操作。比如一般的加法操作,一次性只能加一个数,但是你要是用上一些向量指令集,就可以一次性操作8个、16个、32个,意味着相同的时间内能操作数据就更多,也就更快,这是很自然的事情。
1.3
硬件加速
算法再好,最多10倍,然而硬件指令是16倍朝上,比如肖伟和弗拉德他们做的优化基本上是借助硬件指令,非常简单粗暴。像马云说的,武功再高,也怕菜刀。
1.4 程序内存分布
Go 的内存分布要大致了解,因为汇编是直接对内存进行的操作,所以你需要对内存的位置,哪个位置存什么东西有所了解。其实 Go 怎么使用内存和其他程序是差不多的。最下面的TEXT是存放可知性代码,DATA 是堆和全局变量。唯一不一样的地方是 Go 没有完全使用系统栈,而是拆成 frame 栈帧,栈帧保证程序存的参数和临时数据。那原来系统的栈拿去干嘛了?Go 的调度器和信号处理都是在系统栈上,不在栈帧上。
2. 汇编语法
汇编语法特点
虽然看起来汇编语法是好复杂,其实是非常简单粗暴的,没有 C++、Java 等一堆术语。对内存直接操作,就是这么简单。实际上Go的汇编语法和Plan9这个操作系统渊源很深,Plan9 操作系统大家可能没有听说过,其实和Go是同一波人做的。
Go 的汇编语法其实很简单,它是准抽象的汇编语言,为什么叫准抽象?Go 本来的汇编语言是希望大一统,有什么X86-64,Arm64,大家只要写一种汇编语言就可以。实现起来后发现大部分做不到,最后只能保留差异,统一了风格,再输出机器码,所以叫准抽象的汇编语言。再有就是它的AT&T的风格,从左到右的写法,就是指令级在最左边,中间设几个参数,然后放目标寄存器或者目标,其他的嘛,各个平台就完全不一样了。
2.1 汇编语法例子
这个函数很复杂,c=a+b,然后返回。第一步怎么做呢?我把这个函数名搬下来,这个英文SB实际上是告诉汇编器是说这个东西是static base,基于静态地址寻址。
刚才讲的TEXT区,这是告诉汇编器说你从这里开始找,不要从别的地方找,汇编器说,行,我直接把地址编进去,就这么简单。
还记得例子里刚才我们看到三个参数,abc,都是 int64,一个 int64多少字节?8个字节哈,所以这个栈帧长24个字节。注意这里有对齐的问题,其他平台不一定是24,不过为了简单理解,我把24放到这里来。
2.2 例子代码讲解
第一步是move指令
就是把一个数据从一个地方挪到另外一个地方,简单就是把ab两个数据放寄存器R1、R2里面,这里面多一个东西,FP,就是 Frame Pointer,刚才讲到栈帧保存参数和临时存储的数据的地方。
这就是就是FP开始寻址,FP指栈帧的最低位。你把a拿出来,从0开始寻,挪到R1里面,把B拿出来,是不是8个字节,然后就把它存到R2里面。
第三步,R3=a+b。
最后把R3里面的数据放回C的参数返回,Return。
大家到现在就已经学会汇编语言了。