Golang 作为一门高性能并发编程语言,尤其适合用于构建高并发和高性能的 Web 服务。但是,在实际应用中,由于 Golang 在垃圾回收(GC)机制上的设计,会导致 GC 的并发压力会影响应用程序的性能,从而导致服务不稳定。因此,在实际应用中,如何减少 GC 的并发压力成为一个重要的问题。
本文将介绍 Golang 在性能优化方面的实践经验,通过减少 GC 并发压力来优化程序性能。我们将从以下几个方面来解决这个问题:
- 优化内存分配
- 尽量避免并发 GC
- 使用 GC 调优工具
优化内存分配
在 Golang 中使用 make() 和 new() 函数分配内存,Golang 自带垃圾回收机制会负责回收无用的内存。但是,频繁的内存分配和回收会增加 GC 的压力,对程序性能造成影响。因此,我们需要尽量减少内存分配,从而减轻 GC 的负担。
- 复用对象池
我们可以使用对象池来复用创建的对象,减少内存分配、回收的次数,从而降低 GC 压力。Golang 中自带的 sync.Pool 就是一种对象池,用于缓存小规模的对象,可以重复利用它们,减少内存分配的次数,如下所示:
// www.javascriptcn.com code example
type Person struct {
Name string
Age int
}
var personPool = &sync.Pool{
New: func() interface{} {
return &Person{}
},
}
// 从对象池中获取 Person
func GetPerson() *Person {
return personPool.Get().(*Person)
}
// 将 Person 归还到对象池中
func PutPerson(p *Person) {
personPool.Put(p)
}- 使用切片的
append()方法
使用 append() 方法可以避免频繁地内存分配和回收。我们可以先预设一个容量,使用该容量创建一个空的切片,然后通过 append() 方法向切片中添加元素,这样就只需要进行一次内存分配,而不是每次添加元素都需要重新分配内存。
// www.javascriptcn.com code example
const initialSize = 10 // 预设长度
func main() {
s := make([]*Person, 0, initialSize) // 创建空的切片,预设容量
for i := 0; i < 1000; i++ {
p := GetPerson()
// 操作 p
s = append(s, p)
}
}尽量避免并发 GC
Golang 的 GC 是离线处理的,当检测到内存使用率达到阈值时,GC 才会启动。因此,尽量避免 GC 的频繁触发,对程序的性能优化至关重要。
- 降低
runtime.GCPercent
runtime.GCPercent 表示当已分配的堆内存占用空间达到(已分配堆内存+已活动堆内存)的百分之多少时启动 GC,GC 会清理掉已经不再被使用的内存。默认情况下,GCPercent 的值为 100,也就是当已分配堆内存占用空间达到已活动堆内存的两倍时会触发 GC。
我们可以通过降低 GCPercent 的值来减少 GC 的频次,不过过于降低也可能会导致内存占用过高。根据应用程序的实际情况进行调整。
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
debug.SetGCPercent(10) // 降低 GC 触发频率
// ...
}- 异常情况下手动触发 GC
在一些特殊的情况下,手动触发 GC 可以降低 GC 的压力。调用 runtime.GC() 可以手动触发 GC,但是这种操作影响程序的性能,应该谨慎使用。
// www.javascriptcn.com code example
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
debug.SetGCPercent(10)
// ...
// 异常情况下手动触发 GC
go func() {
for {
time.Sleep(time.Minute * 5) // 每 5 分钟触发一次 GC
runtime.GC()
}
}()
}使用 GC 调优工具
Golang 提供了一些工具,帮助我们调优 GC 的行为和参数。
GODEBUG=gctrace=1
使用 GODEBUG=gctrace=1 环境变量可以打印出 GC 的信息,可以用于分析 GC 的行为和效率。在实际生产环境中,应该先观察 GC 的情况,根据 GC 的行为和效率,确定符合实际生产场景的 GC 调优方案。
$ GODEBUG=gctrace=1 go run main.go gc 1 @0.013s 0%: 0.014+0.13+0.007 ms clock, 0.17+0.12/0.15/0.13+0.084 ms cpu, 4->4->2 MB, 5 MB goal, 16 P gc 2 @0.706s 0%: 0.012+0.28+0.003 ms clock, 0.15+0.30/0.11/0.37+0.031 ms cpu, 8->8->7 MB, 9 MB goal, 16 P ...
go-torch
使用 go-torch 工具可以从火焰图的形式展现出来,这样 GC 行为和对性能影响的展示更清晰。
// www.javascriptcn.com code example $ go get github.com/uber/go-torch $ go-torch --binaryname=main (file) .../github.com/yourname/testperf/test.svg INFO | ignore stack trace: unknown signal Using flamegraph for svg output --> Collecting 60 samples / Use 60 samples for the flame graph / Running the profile for 1 seconds ... INFO | ignore stack trace: unknown signal INFO | ignore stack trace: unknown signal INFO | ignore stack trace: unknown signal INFO | ignore stack trace: unknown signal INFO: Complete
本文介绍了 Golang 在性能优化方面的一些经验,并详细介绍了如何减少 GC 的并发压力来优化程序性能。通过优化内存分配、避免并发 GC 和使用 GC 调优工具,我们可以减轻和控制 GC 的负担,提高应用程序的性能,使之更加稳定高效。
Source: FunTeaLearn,Please indicate the source for reprints https://funteas.com/post/67d73c21a941bf7134d14f4b