查看 Golang、Lua、JS、Rust、Python等语言生成的汇编代码
喜欢的话可以收藏转发加关注
为什么写这篇文章?
昨天在技术群上,有人问了个问题:
如果一个结构体, 只是读里面的成员, 在 golang 里面传值的时候, 不传递指针, golang 编译器会帮你优化成 const & 么?
随便一猜:golang 肯定是直接 copy 整个结构体。
为了确认是否真的是这样,最直白的方式就是直接看 golang 生成的汇编代码。
从图中的汇编代码中,我们可以清楚的看到:golang 的确是执行了完整的结构体 copy 。
然后群友给了这样的反馈...
看着自己日益升高的发际线,我陷入了沉思...
好了,进入正题。
本文将以 1 + ... + 100 的代码为例,介绍以下几种语言查看“汇编代码”的方式。
(这里的“汇编代码”只是个统称,大家不用太计较)
- GolangLuaJavaScript(V8)RustPython等等 ...
1. Golang 生成汇编代码
源码
package main func main(){ var sum = 0 for i:=1 ; i <= 100; i++ { sum = sum + i } }
查看方式
go tool compile -S .\test.go go tool objdump .\test.o
分析
行 1 : 表示 将数值 1 放到 AX 中
行 2 : 表示 跳转到 行 4
行 3 : 表示 对 AX 中的数值执行 + 1 操作
行 4 : 比较 AX 是否在 100 以内。(0x64 是 数值 100 的 16 进制)
行 5 : 跳转到 行 3
嗯... 怎么感觉就是在空循环,我们的 sum 变量哪里去了?
事实上:golang 检测到 sum 没有被使用,直接就帮我们优化掉了,只留下一个空循环。
(但它没有彻底的帮我们把这个空循环也删掉...)
如果我们把代码改成这样子:
package main func main(){ } func getSum()int{ var sum = 0 for i:=1 ; i <= 100; i++ { sum = sum + i } return sum }
那么 sum 累加部分的汇编就是可以正常的显示出来,如图所示:
2. Lua 生成汇编代码
源码
function getSum() local sum = 0 for i=1, 100 do sum = sum + i end return sum end
查看方式
luac.exe -l test.lua
分析
Lua 虚拟机是基于寄存器来实现的。这段汇编代码读起来,就不像golang那么好理解了。
(关于寄存器虚拟机的相关内容,我在文章的评论中补充了一些。有兴趣的话,可以看看)
这里我就简单的分析下:
行 1-4:将常量表里的 0,1,100,1分别加载到寄存器中。
LOADK 指令后面的跟着 2 个参数,分别是:参数1 寄存器索引,参数2 常量表索引
分号后面的数值是: 具体的常量值
这四个数字中:
第一个数字 0 ,就是 sum 的初始值 。
后面三个数(1, 100, 1):表示了循环从 1 开始,到 100 结束,步长为 1
( 也就是说 i 从 1 开始,每次循环自动+1,直到达到 100)
行 5-7:执行循环
Lua 通过 FORPREP、FORLOOP 两条指令来实现循环。
它们的第一个参数:表示指向 循环所需的三个数字 的起始寄存器索引,也就是 寄存器 1。
(这样虚拟机就知道了,循环所需的三个数字:1,100,1,从而准确的控制循环的逻辑)
它们的第二个参数,表示PC指令跳转的距离。
注意:FORLOOP 会把 i+1 的结果 放在寄存器 4 中,对应 add 的第 3 个参数
行 6:执行 Add 操作
Add 后面跟着 3 个参数。含义如下:
参数1:存放结果的寄存器索引
参数2、参数3: 分别是两个加数的索引位置
既然已经分析了 golang 和 lua 两个语言生成的汇编代码,后面的语言就不再详细的分析了,基本大同小异。
3. JavaScript 生成汇编代码
function getSum(){ let sum = 0; for(let i = 1 ; i <= 100; i++){ sum += i } return sum }
查看方式
node --print-bytecode .\test.js
4. Rust 生成汇编代码
rust 就比较有意思了(和 c++ 差不多),值得稍微提一下。
fn main() { println!("{}", get_sum()); } fn get_sum() -> i32{ let mut sum = 0; for i in 1..=100 { sum += i } return sum }
查看方式
rustc -O --emit asm=test_rust.s .\test_rust.rs
加入了 -O 优化之后,生成的汇编代码是这个样子,它直接用 5050 来赋值,省略了计算的过程。
5. Python 生成汇编代码
import dis def getSum(): sum = 0 for i in range(1,101): sum = sum + i return sum dis.dis(getSum)
查看方式
python .\test.py # 也可以通过 python -m py_compile .\test.py 来编译出 pyc 文件 # 再通过第三方工具,来查看 pyc 的字节码
其它语言:有时间再补充
...
最后
那么了解汇编层面的东西,有什么用呢?
嗯...对于绝大多数开发者来说,的确没什么用...
但是如果你有深入底层的追求,那么了解汇编的知识,会让你对程序理解的更加透彻。
而且,不管是 Rust,还是 C++ 都支持 内联汇编:可以在代码里嵌入汇编语言,直接控制 CPU,从而获取极致的底层操作、以及性能上的提升。
比如:腾讯开源的C++协程库:Tencent/libco 就用到了该技术方案。
学习C/C++的伙伴可以私信回复小编“学习”领取全套免费C/C++学习资料、视频