最近给 LuaJIT 实现一个新功能,在性能优化过程中,通过一点小改动,让 JIT 编译器少生成一行机器指令,性能提升了两个数量级 ...
具体是这样子的,JIT 编译器生成出来的机器码,其中主要的部分是这样子的:
mov rbp, [rcx] // 优化后,少了这一行了
cmp rbp, +0x05
jb 0x16ae0018 ->2
add rbp, -0x05
mov [rcx], rbp
是的,只是少了一行内存读取指令,性能却可以提升两个数量级。
接下来我们分析一下其中的缘由:
内存读取慢
首先对于 CPU 而言,肯定是多了一个内存读取的操作。但是多了这么一个操作,应该不至于产生数量级的性能差异的。
LuaJIT 是单线程的,读写内存应该是在操作 “独享” 的 CPU cache,这个还是很快的。
而且原来也是有一个写内存操作的,如果是提升了一倍,还是可以理解的,有这么大的差距应该还是另有原因。
流水线阻塞
我们仔细分析下 mov rbp, [rcx]
后面的几条指令,全部都依赖 rbp
这个寄存器的值。
这是一个典型的 “先读后写” 的依赖,如果内存没有读取到寄存器 rbp
中,后续几条指令全部都会因为缺乏操作数,导致 CPU 流水线阻塞等待。
对于现代 CPU 十几级的流水线而言,流水线阻塞的影响可就大多了。
总结
这种指令级的优化,需要对 CPU 内部的执行细节有清楚的了解,而 CPU 实际执行的具体过程又没有直观的过程信息,只能多做实验,通过执行时间的差异来对比分析了。
对于现代 CPU,其实硬件部分就已经很 “智能” 了,CPU 内部也会 “并发” 执行没有依赖的机器指令,也就是常言的乱序执行。所以,有些时候指令数量增加了,但是执行时间不一定会成比例增加。
但是,如果指令之间有依赖关系,那就会导致流水线的阻塞等待。这里的例子,其实并不是内存读取很慢,而是因为内存的读写操作,导致了指令之间的依赖。