今天小编为大家分享一篇关于 Golang 循环汇编分析的文章,文章中介绍了 golang 循环的汇编层面的处理,通过分析,我们可以更了解循环的实现。希望能对大家有所帮助。
循环是编程中很强大的一个概念,而且非常容易处理。但是,必须将其翻译成机器可理解的基本指令。它的编译方式也可能影响标准库中的其他组件。让我们开始分析一下范围循环。
循环汇编
范围循环可以迭代数组,切片或通道。下面函数展示了,对分片进行循环并将数字相加:
执行 go tool compile -S main.go 可以转储生成汇编代码,下面为范围循环的相关代码。
我们把指令分为两部分:初始化及循环本身。最开始两行指令用来初始化两个寄存器为 0。
寄存器 AX 包含循环中的当前位置,而 CX 包含变量 t 的值。下面是带有指令和通用寄存器的直观表示:
该循环指令 JMP 82 开始,表示跳转到指令 82。可以通过第二列来标识此目标指令:
下一条指令 CMPQ AX, $5 表示“比较寄存器 AX 和数值 5”。它实际上是从 AX 中减去寄存器 DX 的值,并将结果存储到另一个寄存器中。现在,可以在下一条指令 JLT 71 中使用该值,该指令表示“如果小于 0,则跳转到指令 71。”下面是更新后的图:
如果条件不满足,则程序将不会跳转执行循环后面的下一条指令。
因此,我们现在有了循环的结构。下面是转换回 Go 的循环:
该循环的主体是缺失的,下面是指令:
第一个指令 MOVQ “”…autotmp_5+16(SP)(AX*8), DX 表示“将内存从源移动到目标”。由以下内容组成:
片段 “”…autotmp_5+16(SP) 其中 SP 是堆栈指针(我们当前的内存栈帧),而 autotmp_* 是自动生成的变量名称。
偏移量 8(在 64 位架构上,int 为 8 位)乘以寄存器 AX 的值,即循环中的当前位置。
由寄存器 DX 表示的,目标现在包含循环的当前值。
然后,INCQ 代表“递增”,并将递增循环的当前位置:
循环体的最后一条指令是 ADDQ DX, CX 表示“将 DX 添加到 CX”。之前我们已经看到 DX 包含循环的当前值,而 CX 是包含变量 t 内容的寄存器:
它将一直循环直到循环计数器到达 5。然后,循环之后的指令显示寄存器 CX 将其值移至 t :
0x0058 00088 (main.go:11) MOVQ CX, “”.t+8(SP)
这是处于最终状态的图:
我们还可以在 Go 中完成循环的翻译:
为这个新程序生成汇编代码,将提供完全相同的输出。
改进
内部转换循环的方式可能会对其他功能(例如 Go 调度程序)产生影响。在 Go 1.10 之前,编译的循环类似于以下代码:
这种实现方式的问题是,当达到 5 时,指针 p 超过了分配的末尾。这个问题使循环不容易被抢占,因为它的主体不安全。循环编译的优化确保它不会创建任何过去的指针。为准备 Go 调度程序中的非合作式抢占而进行了此改进。
本文转载自 360 云计算公众号。
原文链接:https://mp.weixin.qq.com/s/J37BmlWhEEeuwVnPck0rIA
评论