用 unsafe 消除 Go 边界检查
Eliminating Go bounds checks with unsafe

在 Go 语言的性能优化中,边界检查消除(BCE)是我最常用的技巧,它能显著减少指令和分支,提升热路径效率。但有时编译器无法自动消除边界检查,这时就需要借助 unsafe 包手动干预。本文以 brotli 库中的 binary.LittleEndian.Uint32 为例,展示如何通过 unsafe.Pointer 和 unsafe.SliceData 绕过边界检查,将函数转化为 leaf 函数,从而移除 CALL 开销。实测表明,unsafe 版本的加载速度比标准库快两倍以上。当然,使用 unsafe 必须确保边界检查确实多余,否则可能引发严重错误。
边界检查消除可能是 Go 世界中最稳健、最高效的优化技术之一。
HN 评论区
22- naruhodo
我喜欢读这类文章,但和往常一样,我也觉得它们读起来很让人抓狂,因为它们没有说明调用约定[0](种类繁多且各不相同)——尤其是参数如何分配到寄存器以及栈帧的布局。
关于 Go 汇编语言的文章[1]尤其令人头疼,因为指令参数的顺序是反的——源操作数在前,目的操作数在后——像 AT&T 语法,但寄存器引用又少了 % 符号,看起来又像 MASM 风格[2]。
那些躲在某个技术帐篷深处写博客的作者,应该对帐篷外没那么深入的读者发发慈悲,简要介绍一下他们默认读者具备的知识。
如果上面有任何错误,请将其视为我困惑的佐证。
[0] https://en.wikipedia.org/wiki/X86_calling_conventions
- ncruces
文章应该注明这一点:你千万不要在 go:build 行里把其他/所有小端平台都加进去。
文章本身没错,但这段代码仅适用于允许未对齐访问的平台。
你可以从 unalignedOK 这里获取 Go 认为对此安全的完整平台列表:https://go.dev/src/cmd/compile/internal/ssa/config.go
你需要的是 unalignedOK 和小端平台的交集。
- pjmlp
> 正如我之前抱怨过的,我希望 Go 能有 nobounds 编译器提示,但它没有,所以我们剩下的唯一可行方案就是使用 unsafe 指针运算。
因为 IT 基础设施需要的就是更多的 CVE 漏洞。