Go Wiki: 编译器与运行时优化

此页面列出了编译器进行的优化。请注意,这些优化不一定由语言规范保证。

接口值

接口值中的零宽度类型

将零宽度类型放入接口值中不会进行分配。

  • gc 1.0+
  • gccgo ?

接口值中的字长大小值

将字长大小或更小且非指针的类型放入接口值中不会进行分配。

  • gc: 1.0-1.3,但在 1.4+ 中
  • gccgo: 从未

string[]byte

通过 []byte 进行 map 查找

对于类型为 map[string]T 的 map m[]byte bm[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 调用。 Issuecommit

  • 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 的一部分。