惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
博客园 - 聂微东
博客园_首页
人人都是产品经理
人人都是产品经理
N
News | PayPal Newsroom
云风的 BLOG
云风的 BLOG
U
Unit 42
T
Tailwind CSS Blog
Recent Announcements
Recent Announcements
Security Archives - TechRepublic
Security Archives - TechRepublic
T
The Blog of Author Tim Ferriss
Stack Overflow Blog
Stack Overflow Blog
The Register - Security
The Register - Security
The Hacker News
The Hacker News
博客园 - Franky
Engineering at Meta
Engineering at Meta
Jina AI
Jina AI
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
F
Fortinet All Blogs
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Check Point Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
有赞技术团队
有赞技术团队
TaoSecurity Blog
TaoSecurity Blog
博客园 - 司徒正美
GbyAI
GbyAI
G
Google Developers Blog
B
Blog
G
GRAHAM CLULEY
Y
Y Combinator Blog
雷峰网
雷峰网
爱范儿
爱范儿
酷 壳 – CoolShell
酷 壳 – CoolShell
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Microsoft Azure Blog
Microsoft Azure Blog
WordPress大学
WordPress大学
V
V2EX
罗磊的独立博客
Know Your Adversary
Know Your Adversary
AWS News Blog
AWS News Blog
T
Troy Hunt's Blog
S
SegmentFault 最新的问题
P
Privacy & Cybersecurity Law Blog
T
Threat Research - Cisco Blogs
H
Help Net Security
N
Netflix TechBlog - Medium
Help Net Security
Help Net Security
L
LangChain Blog
D
Docker

重归混沌的BLOG

给silly实现了一个ernro模块 | 重归混沌的BLOG 给silly实现了一个ernro模块 | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG 第一次在生产环境使用 Vibe Coding | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG API 设计的艰难抉择 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 十年 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 在Go语言中如何使XML加载内存无限趋近于0 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG 对跨服玩法中的分布式一致性问题进行简单抽象 | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG Go语言逃逸分析之slice和map | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 谈谈观测 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 写了个AI Agent服务端 | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 谈谈代码设计中“严丝合缝” | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 | 重归混沌的BLOG 一次艰难的线上游戏服务器内存排查经历 如何基于LanguageServerProtocol来编写lint工具 谈谈游戏服务器中RPC模块的设计 谈谈游戏服务器代码抽象 最近碰到的一个分布式一致性问题 谈谈游戏服务器的自动化测试 对Raft协议的一点理解 使用mmap来学习/proc/pid/smaps 2023(完) 再次实现了一个Lua性能分析器 终于给Silly的定时器增加了取消功能 一次虚拟内存排查经历 游戏服务器分布式数据的一种同步的思路 为silly增加了互斥锁 2022(完) 一例误用unsafe包引起的内存问题 Go语言之内存篇 初识Go语言 重新抽象图形API 给Lua实现了一个数学库 谈谈跨平台图形API的抽象 寻路和Flocking算法的结合 行为树的一种高效实现 内测过程中Shader出现的问题 彻底解决多国语言 谈谈数据库的选型 再谈Lua热更新(终) 初窥Rust 关于游戏服务器的服务拆分 ECS的初步实现 ECS初探 屏幕空间(SreenSpace)的想象力 一些对辐射度量学的理解 深度缓冲和半透明渲染 Mysql的间隙锁 更新一些GPU相关知识 2020 地形渲染之爬过的坑 Lua5.3 GC源码阅读(5) 实现一个数据库存储队列 再学计算机图形学入门 再谈分布式服务架构 游戏上线一个月后的反思 一次并发Bug 双向链表的三种实现 谈谈随机数的使用 再谈性能优化 2019 Lua中的函数式编程 重构登录逻辑 Unity资源管理(续) 谈谈Unity的资源管理 一次关于Cache的性能分析 历史之2018 DC3算法 移动平台native代码遭遇的坑 从CPU层面谈谈优化 开卷有益(UNIX编程艺术篇) GC竞争问题 通过Mesh投影来实现贴花系统 谈谈我对数据同步的理解 又一个类型提升引起的Bug Lua5.3 GC源码阅读(4) Lua5.3 GC源码阅读(3) Lua5.3 GC源码阅读(2) Lua5.3 GC源码阅读(1) 三角形光栅化时遇到的坑 一次git事故 再见2017 又一个lua调试器 客户端缓存落地方案 Paxos算法 HTTP服务器的特点 一次性能优化经历 关于CPU分支预测 C程序中让两个不同版本的库共存 实现了一个AOI模块 一个高可伸缩的游戏服务器架构 关于网络协议封装的一些新想法
Go语言之闭包篇
重归混沌 · 2022-12-11 · via 重归混沌的BLOG

在有GC和闭包实现的语言中,我最熟悉的是Lua语言。所以在使用Go语言时,碰到不熟悉的细节,总是会以Lua的机制来对比。

然而由于动态语言和静态语言的区别(静态语言总是有更多优化的机制), 以至于很多时候会得出错误的结论。

比如下面代码:

package main
import "os"
func exist(list []int, f func(n int)bool) bool {
    for _, n := range list {
        if f(n) == true {
            return true
        }
    }
    return false
}
func main() {
    count := len(os.Args)
    a := make([]int, 0)
    for i := 0; i < count; i++ {
        a = append(a, i)
    }
    exist(a, func(n int) bool {
        return n == (count - 1)
    })
}

这段代码定义了一个闭包,然后作为参数传给exist函数。

按照Lua的经验,定义闭包肯定是需要malloc内存。然而Go语言反手就教我做人。

使用go run -gcflags="-m -l" a.go可以发现,这个闭包并没有被分配在堆上。

再使用go tool compile -N -l -S a.go来看一下与闭包相关的Plan9汇编代码。

"".exist STEXT size=234 args=0x28 locals=0x58
    ................
    0x0085 00133 (a.go:5)   MOVQ    "".f+120(SP), DX
    0x008a 00138 (a.go:5)   MOVQ    AX, (SP)
    0x008e 00142 (a.go:5)   MOVQ    (DX), AX
    0x0091 00145 (a.go:5)   PCDATA  $1, $1
    0x0091 00145 (a.go:5)   CALL    AX
    0x0093 00147 (a.go:5)   MOVBLZX 8(SP), AX
    0x0098 00152 (a.go:5)   MOVB    AL, ""..autotmp_5+23(SP)
    0x009c 00156 (a.go:5)   NOP
    0x00a0 00160 (a.go:5)   TESTB   AL, AL
    0x00a2 00162 (a.go:5)   JNE     166
    0x00a4 00164 (a.go:5)   JMP     184
    0x00a6 00166 (a.go:6)   MOVB    $1, "".~r2+128(SP)
    0x00ae 00174 (a.go:6)   MOVQ    80(SP), BP
    0x00b3 00179 (a.go:6)   ADDQ    $88, SP
    0x00b7 00183 (a.go:6)   RET
    0x00b8 00184 (a.go:5)   PCDATA  $1, $-1
    0x00b8 00184 (a.go:5)   JMP     186
    0x00ba 00186 (a.go:5)   JMP     188

"".main STEXT size=372 args=0x0 locals=0x90
    ................
    0x00ff 00255 (a.go:17)  XORPS   X0, X0
    0x0102 00258 (a.go:17)  MOVUPS  X0, ""..autotmp_4+88(SP)
    0x0107 00263 (a.go:17)  LEAQ    ""..autotmp_4+88(SP), AX
    0x010c 00268 (a.go:17)  MOVQ    AX, ""..autotmp_6+104(SP)
    0x0111 00273 (a.go:17)  TESTB   AL, (AX)
    0x0113 00275 (a.go:17)  LEAQ    "".main.func1(SB), CX
    0x011a 00282 (a.go:17)  MOVQ    CX, ""..autotmp_4+88(SP)
    0x011f 00287 (a.go:17)  TESTB   AL, (AX)
    0x0121 00289 (a.go:17)  MOVQ    "".count+72(SP), AX
    0x0126 00294 (a.go:17)  MOVQ    AX, ""..autotmp_4+96(SP)
    0x012b 00299 (a.go:17)  MOVQ    "".a+112(SP), AX
    0x0130 00304 (a.go:17)  MOVQ    "".a+120(SP), CX
    0x0135 00309 (a.go:17)  MOVQ    "".a+128(SP), DX
    0x013d 00317 (a.go:17)  MOVQ    AX, (SP)
    0x0141 00321 (a.go:17)  MOVQ    CX, 8(SP)
    0x0146 00326 (a.go:17)  MOVQ    DX, 16(SP)
    0x014b 00331 (a.go:17)  MOVQ    ""..autotmp_6+104(SP), AX
    0x0150 00336 (a.go:17)  MOVQ    AX, 24(SP)
    0x0155 00341 (a.go:17)  CALL    "".exist(SB)

"".main.func1 STEXT nosplit size=54 args=0x10 locals=0x10
    0x0000 00000 (a.go:17)  TEXT    "".main.func1(SB), NOSPLIT|NEEDCTXT|ABIInternal, $16-16
    0x0000 00000 (a.go:17)  SUBQ    $16, SP
    0x0004 00004 (a.go:17)  MOVQ    BP, 8(SP)
    0x0009 00009 (a.go:17)  LEAQ    8(SP), BP
    0x000e 00014 (a.go:17)  FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x000e 00014 (a.go:17)  FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x000e 00014 (a.go:17)  MOVQ    8(DX), AX
    0x0012 00018 (a.go:17)  MOVQ    AX, "".count(SP)
    0x0016 00022 (a.go:17)  MOVB    $0, "".~r1+32(SP)
    0x001b 00027 (a.go:18)  MOVQ    "".count(SP), AX
    0x001f 00031 (a.go:18)  DECQ    AX
    0x0022 00034 (a.go:18)  CMPQ    "".n+24(SP), AX
    0x0027 00039 (a.go:18)  SETEQ   "".~r1+32(SP)
    0x002c 00044 (a.go:18)  MOVQ    8(SP), BP
    0x0031 00049 (a.go:18)  ADDQ    $16, SP
    0x0035 00053 (a.go:18)  RET

上面的代码并不算太复杂,我们大致可以翻译出他的等价Go语言(翻译出来的代码是可以被编译运行的)。

package main
import "os"
type Closure1 struct {
    F func(int) bool
    n int
}

var DX *Closure1

func func1(n int) bool {
    x := DX.n - 1
    return x == n
}
func exist(list []int, f *Closure1) bool {
    for _, n := range list {
        DX = f
        if f.F(n) == true {
            return true
        }
    }
    return false
}
func main() {
    count := len(os.Args)
    a := make([]int, 0)
    for i := 0; i < count; i++ {
        a = append(a, i)
    }
    c := &Closure1{
        F: func1,
        n: count,
    }
    exist(a, c)
}

从上面的Go代码可以很清楚的看到,其实一个闭包到底分配不分配内存,关键就在于Closure1在栈上还是在堆上。

当Closure1结构暴露出来之后,一切都是那么的显然。

即然闭包是一个struct对象,那么Go当然可以和一般的自定义struct一样进行逃逸分析,而根据逃逸规则,这里的c对象显然不需要逃逸。

一切都很完美,只是还有一个问题没有解决。

exist在调用f函数时,是如何区分调用的是闭包还是非闭包,比如下面代码:

package main
import "os"
func exist(list []int, f func(n int)bool) bool {
        for _, n := range list {
                if f(n) == true {
                        return true
                }
        }
        return false
}
func foo(n int) bool {
        return n == 3
}
func main() {
        count := len(os.Args)
        a := make([]int, 0)
        for i := 0; i < count; i++ {
                a = append(a, i)
        }
        exist(a, foo)
}

再来看一下对应的汇编代码:

"".exist STEXT size=234 args=0x28 locals=0x58
    .......
    0x0085 00133 (a.go:5)   MOVQ    "".f+120(SP), DX
    0x008a 00138 (a.go:5)   MOVQ    AX, (SP)
    0x008e 00142 (a.go:5)   MOVQ    (DX), AX
    0x0091 00145 (a.go:5)   PCDATA  $1, $1
    0x0091 00145 (a.go:5)   CALL    AX
    0x0093 00147 (a.go:5)   MOVBLZX 8(SP), AX
    0x0098 00152 (a.go:5)   MOVB    AL, ""..autotmp_5+23(SP)
    0x009c 00156 (a.go:5)   NOP
    0x00a0 00160 (a.go:5)   TESTB   AL, AL
    0x00a2 00162 (a.go:5)   JNE     166
    0x00a4 00164 (a.go:5)   JMP     184
    .......

"".main STEXT size=300 args=0x0 locals=0x78
    0x00ea 00234 (a.go:20)  MOVQ    "".a+88(SP), AX
    0x00ef 00239 (a.go:20)  MOVQ    "".a+96(SP), CX
    0x00f4 00244 (a.go:20)  MOVQ    "".a+104(SP), DX
    0x00f9 00249 (a.go:20)  MOVQ    AX, (SP)
    0x00fd 00253 (a.go:20)  MOVQ    CX, 8(SP)
    0x0102 00258 (a.go:20)  MOVQ    DX, 16(SP)
    0x0107 00263 (a.go:20)  LEAQ    "".foo·f(SB), AX
    0x010e 00270 (a.go:20)  MOVQ    AX, 24(SP)
    0x0113 00275 (a.go:20)  CALL    "".exist(SB)

通过对比可以发现,其实exist函数的代码并没有任何变化,有变化的代码是a.go第20行。

再来将汇编翻译成Go语言:

package main
import "os"
type Closure1 struct {
    F func(int) bool
}
var DX *Closure1
func foo(n int) bool {
    return n == 3
}
func exist(list []int, f *Closure1) bool {
    for _, n := range list {
        DX = f
        if f.F(n) == true {
            return true
        }
    }
    return false
}
func main() {
    count := len(os.Args)
    a := make([]int, 0)
    for i := 0; i < count; i++ {
        a = append(a, i)
    }
    c := &Closure1{
        F: foo,
    }
    exist(a, c)
}

通过对比两次翻译后的Go语言,可以发现一件很有意思的事。

Go语言其实把所有函数都抽象成闭包,这一点倒是与Lua有颇多相似之处。

只是没有任何值捕获的闭包,在逃逸分析时可以做更多的优化。