go-performance

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Performance Patterns

Go语言性能优化模式

Source: Uber Go Style Guide
Performance-specific guidelines apply only to the hot path. Don't prematurely optimize—focus these patterns where they matter most.

来源:Uber Go 风格指南
性能相关的准则仅适用于热点路径。不要过早进行优化——将这些模式应用在真正有需要的地方。

Prefer strconv over fmt

优先使用strconv而非fmt

When converting primitives to/from strings,
strconv
is faster than
fmt
.
Source: Uber Go Style Guide
Bad:
go
for i := 0; i < b.N; i++ {
    s := fmt.Sprint(rand.Int())
}
Good:
go
for i := 0; i < b.N; i++ {
    s := strconv.Itoa(rand.Int())
}
Benchmark comparison:
ApproachSpeedAllocations
fmt.Sprint
143 ns/op2 allocs/op
strconv.Itoa
64.2 ns/op1 allocs/op

在将基本类型与字符串相互转换时,
strconv
fmt
更快。
来源:Uber Go 风格指南
不良写法:
go
for i := 0; i < b.N; i++ {
    s := fmt.Sprint(rand.Int())
}
推荐写法:
go
for i := 0; i < b.N; i++ {
    s := strconv.Itoa(rand.Int())
}
基准测试对比:
写法速度内存分配
fmt.Sprint
143 ns/op2 allocs/op
strconv.Itoa
64.2 ns/op1 allocs/op

Avoid Repeated String-to-Byte Conversions

避免重复的字符串转字节切片操作

Do not create byte slices from a fixed string repeatedly. Instead, perform the conversion once and capture the result.
Source: Uber Go Style Guide
Bad:
go
for i := 0; i < b.N; i++ {
    w.Write([]byte("Hello world"))
}
Good:
go
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
    w.Write(data)
}
Benchmark comparison:
ApproachSpeed
Repeated conversion22.2 ns/op
Single conversion3.25 ns/op
The good version is ~7x faster because it avoids allocating a new byte slice on each iteration.

不要反复从固定字符串创建字节切片。应只执行一次转换并复用结果。
来源:Uber Go 风格指南
不良写法:
go
for i := 0; i < b.N; i++ {
    w.Write([]byte("Hello world"))
}
推荐写法:
go
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
    w.Write(data)
}
基准测试对比:
写法速度
重复转换22.2 ns/op
单次转换3.25 ns/op
推荐写法的速度约快7倍,因为它避免了在每次循环中分配新的字节切片。

Prefer Specifying Container Capacity

优先指定容器容量

Specify container capacity where possible to allocate memory up front. This minimizes subsequent allocations from copying and resizing as elements are added.
Source: Uber Go Style Guide
尽可能指定容器容量,提前分配内存。这能减少后续添加元素时因复制和扩容产生的内存分配。
来源:Uber Go 风格指南

Map Capacity Hints

Map容量提示

Provide capacity hints when initializing maps with
make()
.
go
make(map[T1]T2, hint)
Note: Unlike slices, map capacity hints do not guarantee complete preemptive allocation—they approximate the number of hashmap buckets required.
Bad:
go
files, _ := os.ReadDir("./files")

m := make(map[string]os.DirEntry)
for _, f := range files {
    m[f.Name()] = f
}
// Map resizes dynamically, causing multiple allocations
Good:
go
files, _ := os.ReadDir("./files")

m := make(map[string]os.DirEntry, len(files))
for _, f := range files {
    m[f.Name()] = f
}
// Map is right-sized at initialization, fewer allocations
使用
make()
初始化Map时提供容量提示。
go
make(map[T1]T2, hint)
注意:与Slice不同,Map的容量提示并不能保证完全预分配内存——它只是近似估算所需的哈希桶数量。
不良写法:
go
files, _ := os.ReadDir("./files")

m := make(map[string]os.DirEntry)
for _, f := range files {
    m[f.Name()] = f
}
// Map会动态扩容,导致多次内存分配
推荐写法:
go
files, _ := os.ReadDir("./files")

m := make(map[string]os.DirEntry, len(files))
for _, f := range files {
    m[f.Name()] = f
}
// Map在初始化时就分配了合适的大小,减少内存分配次数

Slice Capacity

Slice容量

Provide capacity hints when initializing slices with
make()
, particularly when appending.
go
make([]T, length, capacity)
Unlike maps, slice capacity is not a hint—the compiler allocates exactly that much memory. Subsequent
append()
operations incur zero allocations until capacity is reached.
Bad:
go
for n := 0; n < b.N; n++ {
    data := make([]int, 0)
    for k := 0; k < size; k++ {
        data = append(data, k)
    }
}
Good:
go
for n := 0; n < b.N; n++ {
    data := make([]int, 0, size)
    for k := 0; k < size; k++ {
        data = append(data, k)
    }
}
Benchmark comparison:
ApproachTime (100M iterations)
No capacity2.48s
With capacity0.21s
The good version is ~12x faster due to zero reallocations during append.

使用
make()
初始化Slice时提供容量提示,尤其是在需要追加元素的场景下。
go
make([]T, length, capacity)
与Map不同,Slice的容量不是提示——编译器会精确分配指定大小的内存。在容量耗尽前,后续的
append()
操作不会产生任何内存分配。
不良写法:
go
for n := 0; n < b.N; n++ {
    data := make([]int, 0)
    for k := 0; k < size; k++ {
        data = append(data, k)
    }
}
推荐写法:
go
for n := 0; n < b.N; n++ {
    data := make([]int, 0, size)
    for k := 0; k < size; k++ {
        data = append(data, k)
    }
}
基准测试对比:
写法耗时(1亿次迭代)
未指定容量2.48s
指定容量0.21s
推荐写法的速度约快12倍,因为追加过程中没有重新分配内存。

Pass Values

传递值类型

Source: Go Wiki CodeReviewComments (Advisory)
Don't pass pointers as function arguments just to save a few bytes. If a function refers to its argument
x
only as
*x
throughout, then the argument shouldn't be a pointer.
Common instances where values should be passed directly:
  • Pointer to a string (
    *string
    ) — strings are already small fixed-size headers
  • Pointer to an interface value (
    *io.Reader
    ) — interfaces are fixed-size (type + data pointers)
Bad:
go
func process(s *string) {
	fmt.Println(*s)  // only dereferences, never modifies
}
Good:
go
func process(s string) {
	fmt.Println(s)
}
Exceptions:
  • Large structs where copying is expensive
  • Small structs that might grow in the future

来源:Go Wiki CodeReviewComments(建议)
不要仅仅为了节省几个字节就将指针作为函数参数传递。如果函数在整个执行过程中仅通过
*x
来引用参数
x
,那么该参数不应该是指针类型。
适合直接传递值类型的常见场景:
  • 字符串指针(
    *string
    )——字符串本身已是固定大小的头部结构
  • 接口值指针(
    *io.Reader
    )——接口是固定大小的(类型指针+数据指针)
不良写法:
go
func process(s *string) {
	fmt.Println(*s)  // 仅解引用,从不修改
}
推荐写法:
go
func process(s string) {
	fmt.Println(s)
}
例外情况:
  • 大型结构体,复制成本较高
  • 未来可能会扩容的小型结构体

Quick Reference

快速参考

PatternBadGoodImprovement
Int to string
fmt.Sprint(n)
strconv.Itoa(n)
~2x faster
Repeated
[]byte
[]byte("str")
in loop
Convert once outside~7x faster
Map initialization
make(map[K]V)
make(map[K]V, size)
Fewer allocs
Slice initialization
make([]T, 0)
make([]T, 0, cap)
~12x faster
Small fixed-size args
*string
,
*io.Reader
string
,
io.Reader
No indirection

模式不良写法推荐写法优化效果
整数转字符串
fmt.Sprint(n)
strconv.Itoa(n)
约2倍提速
重复创建
[]byte
循环内执行
[]byte("str")
提前转换一次并复用约7倍提速
Map初始化
make(map[K]V)
make(map[K]V, size)
减少内存分配次数
Slice初始化
make([]T, 0)
make([]T, 0, cap)
约12倍提速
小型固定大小参数
*string
,
*io.Reader
string
,
io.Reader
避免间接引用

See Also

参考链接

  • For core style principles:
    go-style-core
  • For naming conventions:
    go-naming
  • 核心风格原则:
    go-style-core
  • 命名规范:
    go-naming