当前位置:网站首页>Go编译原理系列8(变量捕获)
Go编译原理系列8(变量捕获)
2022-08-04 11:31:00 【书旅】
前言
在前边的几篇文章中已经基本分享完了编译器前端的一些工作,后边的几篇主要是关于编译器对抽象语法树进行分析和重构,然后完成一系列的优化,其中包括以下五个部分:
变量捕获 函数内联 逃逸分析 闭包重写 遍历函数
后边的五篇文章主要就是上边这五个主题,本文分享的是变量捕获,变量捕获主要是针对闭包场景的,因为闭包函数中可能引用闭包外的变量,因此变量捕获需要明确在闭包中通过值引用或地址引用的方式来捕获变量
变量捕获概述
下边通过一个示例来看一下什么是变量捕获
package main
import (
"fmt"
)
func main() {
a := 1
b := 2
go func() {
//在闭包里对a或b进行了重新赋值,也会改变引用方式
fmt.Println(a, b)
}()
a = 666
}
我们可以看到在闭包中引用了外部的变量a、b,由于变量a在闭包之后进行了其他赋值操作,因此在闭包中,a、b变量的引用方式会有所不同。在闭包中,必须采取地址引用的方式对变量a进行操作,而对变量b的引用将通过直接值传递的方式进行
我们可以通过如下方式查看当前程序闭包变量捕获的情况
go tool compile -m=2 main.go | grep capturing
assign=true代表变量a在闭包完成后又进行了赋值操作
也可以看一个稍微复杂的
func adder() func(int) int {//累加器
sum := 0 //地址引用
return func(v int) int {
sum += v
return sum
}
}
func main() {
a := adder()
for i:=0;i<10;i++{
fmt.Printf("0 + 1 + ... + %d = %d\n", i, a(i))
}
}
上一篇文章分享了类型检查,我们可以继续顺着编译的入口文件中类型检查后边的代码往下看,你会看到如下这段代码
编译入口文件:src/cmd/compile/main.go -> gc.Main(archInit)
// Phase 4: Decide how to capture closed variables.(决定如何捕获闭包变量)
// This needs to run before escape analysis,
// because variables captured by value do not escape.(变量捕获应该在逃逸分析之前进行,因为值类型的变量捕获,不会进行逃逸分析)
timings.Start("fe", "capturevars")
for _, n := range xtop {
if n.Op == ODCLFUNC && n.Func.Closure != nil { //函数需要是闭包类型
Curfn = n
capturevars(n)
}
}
capturevarscomplete = true
从上边这段代码及注释中,我们可以得到以下几个信息:
变量捕获应该在逃逸分析之前进行,因为值类型的变量捕获,不会进行逃逸分析 变量捕获是针对闭包函数的 变量捕获的实现主要是调用了:src/cmd/compile/internal/gc/closure.go→ capturevars
下边我们就去看capturevars
方法的内部实现,了解变量捕获的一些细节
变量捕获底层实现
所有类型检查完成后,capturevars将在单独的阶段调用,它决定闭包捕获的每个变量是通过值还是通过引用捕获
func capturevars(xfunc *Node) {
......
clo := xfunc.Func.Closure
cvars := xfunc.Func.Cvars.Slice()
out := cvars[:0]
for _, v := range cvars {
......
out = append(out, v)
......
outer := v.Name.Param.Outer
outermost := v.Name.Defn
// out parameters will be assigned to implicitly upon return.
if outermost.Class() != PPARAMOUT && !outermost.Name.Addrtaken() && !outermost.Name.Assigned() && v.Type.Width <= 128 {
v.Name.SetByval(true)
} else {
outermost.Name.SetAddrtaken(true)
outer = nod(OADDR, outer, nil)
}
......
outer = typecheck(outer, ctxExpr)
clo.Func.Enter.Append(outer)
}
xfunc.Func.Cvars.Set(out)
lineno = lno
}
该方法的代码量很少,大致内容就是,它会先获取到闭包函数内所有变量节点,然后对这些节点进行遍历。确定该闭包需要捕获的变量之后再没有被修改时,且该变量小于128字节,则会认为他是值引用。后边它会对外部引用的结点进行类型检查
总结
本部分比较简单,但是挺实用的,特别是我这种一直搞不明包闭包引用外部变量的人。后边的逃逸分析、闭包重写跟变量捕获有一定的联系,介绍的后边内容的时候再提
边栏推荐
猜你喜欢
傅里叶级数与傅里叶变换学习
200PLC转以太网与研华webaccess modbusTCP客户端在空调机上应用配置案例
【LeetCode】98.验证二叉搜索树
God Space - the world's first Web3.0-based art agreement creative platform, broadening the boundaries of multi-art integration
Using .NET to simply implement a high-performance clone of Redis (2)
The use of DDR3 (Naive) in Xilinx VIVADO (2) Read and write design
Learn to use the basic interface of set and map
命令模式(Command)
Zikko launches new Thunderbolt 4 docking station with both HDMI2.1 and 2.5GbE
Leetcode——利用先序遍历特性完成114. 二叉树展开为链表
随机推荐
Zhihu Data Analysis Training Camp
关于架构的思考
命令模式(Command)
Leetcode刷题——543. 二叉树的直径、617. 合并二叉树(递归解决)
中电金信技术实践|分布式事务简说
北京大学,新迎3位副校长!其中一人为中科院院士!
【LeetCode】232.用栈实现队列
*W3C* 标准组织
【LeetCode】653. 两数之和 IV - 输入 BST
复盘:经典的HR面试问题,这些问题可以挖掘你个人的素质,看看你是否合适合我们部门
少即是多:视觉SLAM的点稀疏化(IROS 2022)
Implementation principle of function emplace_back in vector
『快速入门electron』之实现窗口拖拽
力扣解法汇总1403-非递增顺序的最小子序列
IBM Q复制新增QSUB
Four ways to traverse a Map
BOSS 直聘回应女大学生连遭两次性骚扰:高度重视求职者安全,可通过 App 等举报
3-5年以上的功能测试如何进阶自动化?
Mysql——》类型转换符binary
Using .NET to simply implement a high-performance clone of Redis (2)