本文总结了8个Golang性能优化技巧,旨在帮助开发者避免常见的性能陷阱。原文: 8 Golang Performance Tips I Discovered After Years of Coding

1. 明智使用 Goroutine

你肯定习惯通过 goroutine 同时运行函数,觉得很酷,对吧?但实际上太多 goroutine 会拖慢运行速度,每个 goroutine 都会占用一定内存,如果程序运行了数百万个 goroutine,就会增加很多内存。

避免以下做法:

for _, item := range bigList {
    go process(item)
}

试着用信号量(semaphore)进行限制:

sem := make(chan struct{}, 100) // 限制 100 个 goroutines
for _, item := range bigList {
    sem <- struct{}{}
    go func(i Item) {
        defer func() { <-sem }()
        process(i)
    }(item)
}

我曾经做过航班搜索项目,该项目具有数百万的搜索规模。我们在很多地方都添加了 goroutine,试图对其进行优化。这时我才意识到 goroutine 会占用大量内存。我们需要持续监控每个点上运行的 goroutine 的数量,并需要建立某种机制,根据性能要求对其进行调整。

2. 谨慎使用 channel

channel 非常适合用于程序之间通信,但也可能很棘手。不带缓冲的 channel 会导致程序在意想不到的时候阻塞。

避免以下做法:

ch := make(chan int)

在需要的时候使用带缓冲的 channel:

ch := make(chan int, 100) // 缓冲大小为 100

带缓冲的 channel 可确保发送方在接收方尚未准备好接受任何数据的情况下不会被卡住,可以帮助我们简化流程,尤其是在处理大规模事务时,通道可能会非常繁忙。

3. 避免使用全局变量

全局变量初看似乎很简单,但可能会带来很多问题。全局变量会让人很难跟踪变化的内容,并可能导致错误。

避免以下做法:

var counter int
func increment() {
    counter++
}

传递变量:

func increment(counter int) int {
    return counter + 1
}
counter := 0
counter = increment(counter)

曾经看到过遍布全局变量的代码,调试时完全是一场噩梦。保持局部化会让代码更简洁、更快速。

4. 高效使用切片

切片是 Golang 中使用最多的数据结构之一,因此我们有责任明智的使用。一不小心,就可能会在切片中拷贝更多数据。

避免不必要的拷贝:

// 不好: 创建新切片
newSlice := make([]int, len(oldSlice))
copy(newSlice, oldSlice)
// 更好: 只使用原始切片
newSlice := oldSlice[:]

此外,在对切片添加数据时,如果知道容量会有多大,请考虑预先分配容量。

预先分配容量:

s := make([]int, 0, 100) // 容量为 100

这有助于避免多次分配内存,加快速度。

5. 分析代码

需要采取某种机制来检查代码与模块的效率,Go 内置工具可以帮助查看程序把时间花在了哪些方面。

使用分析工具(profiler):

import (
    "runtime/pprof"
    "os"
)
func main() {
    f, _ := os.Create("cpu.prof")
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()
    
    // 业务代码
}

运行分析工具后,可能会发现某个你认为很快的函数实际上拖慢了速度。修复后,程序可以运行得更快了。

6. 使用标准库

Go 有一些漂亮的库,而我犯的一个错误就是在项目中引入了很多 Go 已经提供的功能。强烈建议你在编写代码之前,先检查一下 Go 是否已经提供了相应功能。

避免编写自己的排序功能:

func mySort(data []int) {
    // 自定义排序
}

使用内置排序软件包:

import "sort"
sort.Ints(data)

以前我经常重新发明轮子,但现在更多依赖标准库,这样代码可以更简短,通常也更快。

7. 注意内存分配

分配内存需要时间。如果在短时间内创建许多临时对象,考虑到创建对象所耗费的时间,很可能会影响性能。

尽可能复用对象:

var buffer bytes.Buffer
for i := 0; i < 1000; i++ {
    buffer.Reset()
    buffer.WriteString("Some data")
    process(buffer.Bytes())
}

通过重复使用 buffer,可以避免每次都分配新的内存,这个技巧可以加快运行多次的循环速度。

8. 随时更新 Go 版本

Go 的每个新版本都会对性能进行改进,总是使用最新的 Go 版本,就能免费获得这些优势。

因为更新看起来会很麻烦,因此很多人会一直使用旧版本,但升级后很有可能会发现程序在不修改任何代码的情况下运行得更快了!

总结

这些是多年来积累的一些小技巧。每个项目都有所不同,请确保解决方案确实能够解决你的问题,而每当面临某种性能瓶颈时,请注意上述提到的方面。

最后,在修改代码后,一定要记得对代码进行测试和性能分析。有时,我们认为的优化可能达不到预期效果。


你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

本文由mdnice多平台发布


俞凡
13 声望11 粉丝

你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起...