一、前言
Go语言作为一种高性能的编程语言,具备了很多优秀的特性和工具,帮助开发人员进行程序性能优化分析。接下来,我们将尝试如何使用这些工具来分析和优化Go程序的性能。
二、go test -bench使用
go test命令是Go语言的测试工具,它可以通过添加-bench标志来运行基准测试。基准测试是一种用于测量代码性能的测试方法。通过运行基准测试,我们可以获得代码的执行时间、内存分配等详细信息。
下面我们创建文件名为 benchmark_test.go 的测试示例,然后运行代码,运行结果为输出性能统计信息。
fibonacci函数是我们要进行基准测试的功能代码。BenchmarkFibonacci函数是我们的基准测试函数。它使用testing.B类型的参数来访问基准测试相关的功能。在这个示例中,我们调用fibonacci函数作为要进行基准测试的代码,并在循环中重复调用它。- 在
main函数中,我们使用testing.Benchmark函数来运行基准测试函数。
package main
import (
"fmt"
"testing"
)
// 要进行基准测试的函数
func fibonacci(n int) int {
if n <= 1 {
return n
}
return fibonacci(n-1) + fibonacci(n-2)
}
// 基准测试函数
func BenchmarkFibonacci(b *testing.B) {
for i := 0; i < b.N; i++ {
fibonacci(10) // 调用要进行基准测试的函数
}
}
func main() {
fmt.Println("Running benchmark...")
testing.Benchmark(BenchmarkFibonacci)
}
运行结果,下图表示程序通过了基准测试
BenchmarkFibonacci-8:表示运行基准测试函数BenchmarkFibonacci的结果。-8表示使用 8 个 CPU 核心进行测试。4275781:表示总共执行了 4275781 次操作(在b.N循环中)。263.3 ns/op:表示每个操作平均耗时 263.3 纳秒。
三、go tool pprof使用
go tool pprof是一个命令行工具,用于分析和可视化由go profiler生成的分析报告。它可以显示函数的CPU使用情况、内存分配情况等,并提供了交互式界面来帮助我们深入了解代码的性能问题。
1、代码示例
下面代码是创建一个数字切片,并对其执行了简单的操作,这里使用了net/http/pprof包来提供性能分析的Web接口。通过调用http.ListenAndServe()函数在端口8080上启动一个HTTP服务器,并将其放在一个goroutine中以便与主程序并发运行。
package main
import (
"fmt"
"math/rand"
"net/http"
_ "net/http/pprof"
)
func GenerateSlice(n int) []int {
s := make([]int, n)
for i := 0; i < n; i++ {
s[i] = rand.Intn(n)
}
return s
}
func SumSlice(s []int) int {
sum := 0
for _, v := range s {
sum += v
}
return sum
}
func main() {
go func() {
http.ListenAndServe(":8080", nil)
}()
for {
s := GenerateSlice(1000000)
fmt.Println(SumSlice(s))
}
}
2、性能分析
要访问性能分析数据,请在浏览器中输入http://localhost:8080/debug/pprof/。这将显示可用的性能分析选项。例如,要查看CPU配置文件,请访问http://localhost:6060/debug/pprof/profile。
接下来我们使用下面命令来获取CPU的profile:
wget http://localhost:8080/debug/pprof/profile?seconds=30 -O cpu.pprof
然后,分析CPU数据:
go tool pprof -http=:8081 cpu.pprof
注意:在Windows上打开网页可能会提示 Could not execute dot; may need to install graphviz.,这个时候我们需要去安装 graphviz 组件,下载地址,我们选择Windows版下载,然后安装,注意安装工程中勾选环境变量,然后到graphviz的安装目录里面的bin下打开管理员cmd执行 dot -c 来生成配置文件
得到火焰图展示
3、分析结论
在上面代码中,存在以下几个潜在问题:
GenerateSlice()函数使用rand.Intn(n)生成随机数。在每次循环迭代中都调用该函数可能会导致较大的开销,因为它需要进行随机数生成。SumSlice()函数对切片进行遍历来计算总和。这可能是一个耗时的操作,特别是当切片很大时。
4、优化代码
优化代码过程:
- 添加了
randomSlice变量来缓存随机数切片。 - 引入了
randomSliceMutex互斥锁来保护对randomSlice的并发访问。 GenerateSlice()函数首先检查缓存中的随机数切片,如果已存在且长度与所需长度相同,则直接返回缓存值。否则,生成新的随机数切片并更新缓存。- 在
main()函数中,使用了sync.WaitGroup和并行化的方式启动了goroutine。
通过这样的修改,随机数切片将在第一次生成后被缓存,并在后续调用中被复用,从而避免了重复生成的开销。请注意,在多个goroutine同时访问和更新randomSlice时,使用互斥锁是为了确保安全的并发访问。
package main
import (
"math/rand"
"net/http"
_ "net/http/pprof"
"sync"
)
var randomSlice []int
var randomSliceMutex sync.Mutex
func GenerateSlice(n int) []int {
randomSliceMutex.Lock()
defer randomSliceMutex.Unlock()
if randomSlice != nil && len(randomSlice) == n {
return randomSlice
}
s := make([]int, n)
for i := 0; i < n; i++ {
s[i] = rand.Intn(n)
}
randomSlice = s
return s
}
func SumSlice(s []int) int {
sum := 0
for _, v := range s {
sum += v
}
return sum
}
func main() {
go func() {
http.ListenAndServe(":8080", nil)
}()
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
s := GenerateSlice(1000000)
SumSlice(s)
}
}()
}
wg.Wait()
}
现在我们继续重复上面性能分析的步骤,获取到我们CPU的profile,对CPU数据进行分析,然后进行火焰图展示
在我们日常的开发中,可以通过pprof很快的找出程序中的性能瓶颈,并对代码进行优化。
四、性能优化总结
- 进行性能分析,使用Go的内置性能分析工具来检测瓶颈和性能问题。
- 确定性能指标,明确要改进的性能指标,以便衡量优化效果。
- 使用基准测试来评估当前性能,并提供优化的参考点。
- 利用并发,使用goroutine和通道实现并行计算和异步I/O,提高程序的响应性和利用多核处理器的能力。
- 减少内存分配,避免过度使用临时变量,复用对象并使用对象池来减少内存分配和垃圾回收的开销。
- 优化算法和数据结构,选择适当的算法和数据结构来提高性能,例如使用散列表代替线性搜索或使用排序和二分查找来加速搜索。
通过性能优化分析和实现,可以提高Go程序的效率、响应时间和可伸缩性,以提供更好的用户体验和更高的系统吞吐量。