Go Wiki: 编译器与运行时优化
此页面列出了编译器进行的优化。请注意,这些优化不一定由语言规范保证。
接口值
接口值中的零宽度类型
将零宽度类型放入接口值中不会进行分配。
- gc 1.0+
- gccgo ?
接口值中的字长大小值
将字长大小或更小且非指针的类型放入接口值中不会进行分配。
- gc: 1.0-1.3,但不在 1.4+ 中
- gccgo: 从未
string
和 []byte
通过 []byte
进行 map 查找
对于类型为 map[string]T
的 map m
和 []byte b
,m[string(b)]
不会进行分配。(不会创建字节切片的临时字符串副本)
- gc 1.4+
- gccgo ?
对 []byte
(s) 进行 range
在将 string
转换为 []byte
以进行字节范围遍历时,不会发生分配
s := "foo"
for i, c := range []byte(s) {
// ...
}
字符串比较转换
在为了比较目的将 []byte
转换为 string
时,不会进行分配
var b1 string
var b2 []byte
var x = string(b1) == string(b2) // memeq
var y = string(b1) < string(b2) // lexicographical comparison
- gc: 1.5+ (CL 3790)
- gccgo ?
逃逸分析和内联
使用 -gcflags -m
观察 gc 工具链的逃逸分析和内联决策结果。
(待定:解释 -gcflags -m
的输出)。
逃逸分析
Gc 编译器会在函数和包边界上进行全局逃逸分析。然而,有很多情况它会放弃。例如,分配给任何类型的间接引用(*p = ...
)都会被认为是逃逸的。其他可能阻碍分析的因素包括:函数调用、包边界、切片字面量、子切片和索引等。完整的规则过于复杂,无法在此描述,请查看 -m
输出。
- gc 1.0+
- gccgo 8.0+.
函数内联
只有简短简单的函数才会被内联。要被内联,函数必须符合以下规则:
- 函数应足够简单,AST 节点数量必须小于预算(80);
- 函数不包含闭包、defer、recover、select 等复杂内容;
- 函数前面没有 `go:noinline` 前缀;
- 函数前面没有 `go:uintptrescapes` 前缀,因为逃逸信息会在内联过程中丢失;
- 函数有函数体;
- 等等。
- gc 1.0+
- gccgo: -O1 及以上。
惯用法
优化的 memclr
对于切片或数组 s,以下形式的循环:
for i := range s {
s[i] = <zero value for element of s>
}
会被转换为高效的运行时 memclr 调用。 Issue 和 commit。
- gc 1.5+
- gccgo ?
不可扫描对象
当元素类型不包含指针时(对于 map,键和值都不包含),垃圾回收器不会扫描切片、通道和 map 的底层缓冲区。这使得可以在内存中存储大量数据集,而不会在垃圾回收时付出高昂代价。例如,以下 map 对 GC 时间的影响不明显
type Key [64]byte // SHA-512 hash
type Value struct {
Name [32]byte
Balance uint64
Timestamp int64
}
m := make(map[Key]Value, 1e8)
- gc 1.5+
- gccgo ?
此内容是 Go Wiki 的一部分。