查看 Golang、Lua、JS、Rust、Python等语言生成的汇编代码

喜欢的话可以收藏转发加关注

为什么写这篇文章?

昨天在技术群上,有人问了个问题:

如果一个结构体, 只是读里面的成员, 在 golang 里面传值的时候, 不传递指针, golang 编译器会帮你优化成 const & 么?

随便一猜:golang 肯定是直接 copy 整个结构体。

为了确认是否真的是这样,最直白的方式就是直接看 golang 生成的汇编代码。

从图中的汇编代码中,我们可以清楚的看到:golang 的确是执行了完整的结构体 copy 。

查看 Golang、Lua、JS、Rust、Python等语言生成的汇编代码

然后群友给了这样的反馈...

查看 Golang、Lua、JS、Rust、Python等语言生成的汇编代码

看着自己日益升高的发际线,我陷入了沉思...


好了,进入正题。

本文将以 1 + ... + 100 的代码为例,介绍以下几种语言查看“汇编代码”的方式。

(这里的“汇编代码”只是个统称,大家不用太计较)

  1. 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

查看 Golang、Lua、JS、Rust、Python等语言生成的汇编代码

分析

行 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 累加部分的汇编就是可以正常的显示出来,如图所示:

查看 Golang、Lua、JS、Rust、Python等语言生成的汇编代码

2. Lua 生成汇编代码

源码

function getSum()
 local sum = 0
 for i=1, 100 do
 sum = sum + i
 end
 return sum
end

查看方式

luac.exe -l test.lua

查看 Golang、Lua、JS、Rust、Python等语言生成的汇编代码

分析

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

查看 Golang、Lua、JS、Rust、Python等语言生成的汇编代码

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 来赋值,省略了计算的过程。

查看 Golang、Lua、JS、Rust、Python等语言生成的汇编代码

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 的字节码

查看 Golang、Lua、JS、Rust、Python等语言生成的汇编代码

其它语言:有时间再补充

...

最后

那么了解汇编层面的东西,有什么用呢?

嗯...对于绝大多数开发者来说,的确没什么用...

但是如果你有深入底层的追求,那么了解汇编的知识,会让你对程序理解的更加透彻。

而且,不管是 Rust,还是 C++ 都支持 内联汇编:可以在代码里嵌入汇编语言,直接控制 CPU,从而获取极致的底层操作、以及性能上的提升。

比如:腾讯开源的C++协程库:Tencent/libco 就用到了该技术方案。

学习C/C++的伙伴可以私信回复小编“学习”领取全套免费C/C++学习资料、视频

查看 Golang、Lua、JS、Rust、Python等语言生成的汇编代码

相关推荐