Golang pprof性能测试与分析讲解

 更新时间:2023年04月04日 11:37:02   作者:qq_42170897  
刚开始接触go就遇到了一个内存问题,在进行内存分析的时候发现了一下比较好的工具,在此留下记录,下面这篇文章主要给大家介绍了关于go性能分析工具pprof的性能测试,需要的朋友可以参考下

一、性能分析类型

1.CPU性能分析

CPU性能分析是最常见的性能分析类型。启动CPU分析时,运行时每隔10ms中断一次,采集正在运行协程的堆栈信息。

程序运行结束后,可以根据收集的数据,找到最热代码路径。

一个函数在分析阶段出现的次数越多,则该函数的代码路径(code path)花费的时间占总运行时间的比重越大。

2.内存性能分析

内存性能分析记录堆内存分配信息,忽略栈内存的分配。

内存分析启动时,默认每1000次采样1次,这个比例是可以调整的。因为内存性能分析是基于采样的,因此基于内存分析数据来判断程序所有的内存使用情况是很困难的。

3.阻塞性能分析

阻塞性能分析是go特点的。

阻塞性能分析用来记录一个协程用来等待共享资源所花费的时间,这用来判断程序并发瓶颈是很有用。阻塞的场景包括:

  • 在没有缓冲的信道上发送或接受数据。
  • 在空的信道上接受数据或在满的信道上发送数据。
  • 尝试获取一个已被其他协程占用的排他锁。

一般情况下,当所有的 CPU 和内存瓶颈解决后,才会考虑这一类分析。

二、cpu性能分析

1.生成pporf

go 性能分析接口位于runtime/pprof 中:

测试代码:生成5组数据,进行冒泡排序:

main.go

// main.go
package main
import (
	"math/rand"
	"time"
)
func generate(n int) []int {
	rand.Seed(time.Now().UnixNano())
	nums := make([]int, 0)
	for i := 0; i < n; i++ {
		nums = append(nums, rand.Int())
	}
	return nums
}
func bubbleSort(nums []int) {
	for i := 0; i < len(nums); i++ {
		for j := 1; j < len(nums)-i; j++ {
			if nums[j] < nums[j-1] {
				nums[j], nums[j-1] = nums[j-1], nums[j]
			}
		}
	}
}
func main() {
	n := 10
	for i := 0; i < 5; i++ {
		nums := generate(n)
		bubbleSort(nums)
		n *= 10
	}
}

想要度量这段代码的性能,只需要在main函数最前加两行代码:

main()

import (
	"math/rand"
	"os"
	"runtime/pprof"
	"time"
)
func main() {
	pprof.StartCPUProfile(os.Stdout)
	defer pprof.StopCPUProfile()
	n := 10
	for i := 0; i < 5; i++ {
		nums := generate(n)
		bubbleSort(nums)
		n *= 10
	}
}

go run main.go > cpu.pprof

当然也可以将输出直接导入到文件中:

2.分析数据

此时得到cpu.pprof 文件:

go tool pprof -http=:9999 cpu.pprof 如果提升Graphviz没有安装: apt installgraphviz (ubuntu)

访问localhost:9999 得到:

除了在网页中查看外,还可以使用交互式命令进行查看:

go tool pprof cpu.pprof

使用top 查看到 bubbleSort函数占用cpu最多。

还可以使用top --cum,按照cum(累计消耗)排序:

使用help 查看帮助:

三、内存性能分析

下面为一段字符串拼接代码,我们对它进行内存分析:

package main
import (
	"math/rand"
	"github.com/pkg/profile"
)
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func randomString(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}
func concat(n int) string {
	s := ""
	for i := 0; i < n; i++ {
		s += randomString(n)
	}
	return s
}
func main() {
	concat(100)
}

我们使用另外一个性能分析库"github.com/pkg/profile" 它内部封装了 runtime/pprof 接口,使用起来更加简单。

cpu性能分析:

defer profile.Start().Stop()

内存性能分析:

defer profile.Start(profile.MemProfile, profile.MemProfileRate(1)).Stop()

profile包会自动在/tmp目录下生成profile文件

go tool pprof -http=:9999 /tmp/profile575547387/mem.pprof

可以看见concat 消耗了 524 KB, 而randomString消耗了 21KB,为什么相差这么大呢?

因为go中的字符串不可修改,使用+ 连接字符串会导致重新生成新的字符串,将 + 两边的子字符串拷贝到新的字符串去。那这种设计多次字符串拼接的场景该如何优化呢?使用strings.Builder

优化后的代码:

package main
import (
	"math/rand"
	"strings"
	"github.com/pkg/profile"
)
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func randomString(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}
func concat(n int) string {
	sb := new(strings.Builder)
	for i := 0; i < n; i++ {
		sb.WriteString(randomString(n))
	}
	return sb.String()
}
func main() {
	defer profile.Start(profile.MemProfile, profile.MemProfileRate(1)).Stop()
	concat(100)
}

优化后可以看到concat 函数使用了71KB 内存,randomString函数使用了 21kb 内存。

四、benchmark 生成 profile

使用benchmark 进行基准测试时,除了直接查看结果,还可以生成profile

testing支持cpu、mem、block

  • -cpuprofile=$FILE
  • -memprofile=$FILE, -memprofilerate=N 调整记录速率为原来的 1/N。
  • -blockprofile=$FILE

fib_test.go

package fib
import "testing"
func fib(n int) int {
	if n == 0 || n == 1 {
		return n
	}
	return fib(n-2) + fib(n-1)
}
func BenchmarkFib(b *testing.B) {
	for n := 0; n < b.N; n++ {
		fib(30) // run fib(30) b.N times
	}
}

go test -bench=. test/bench/fib -cpuprofile=cpu.pprof

go tool pprof -test cpu.pprof

go tool pprof 支持多种输出格式:

go tool pprof

到此这篇关于Golang pprof性能测试与分析讲解的文章就介绍到这了,更多相关Go pprof性能测试内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Golang利用Template模板动态生成文本

    Golang利用Template模板动态生成文本

    Go语言中的Go Template是一种用于生成文本输出的简单而强大的模板引擎,它提供了一种灵活的方式来生成各种格式的文本,下面我们就来看看具体如何使用Template实现动态文本生成吧
    2023-09-09
  • 一文带你了解Golang中interface的设计与实现

    一文带你了解Golang中interface的设计与实现

    本文就来详细说说为什么说 接口本质是一种自定义类型,以及这种自定义类型是如何构建起 go 的 interface 系统的,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-01-01
  • 在Go中实现高效可靠的链路追踪系统

    在Go中实现高效可靠的链路追踪系统

    在当今互联网应用的架构中,分布式系统已经成为主流,分布式系统的优势在于能够提供高可用性、高并发性和可扩展性,本文将介绍链路追踪的概念和原理,并重点介绍如何在Golang中实现高效可靠的链路追踪系统,需要的朋友可以参考下
    2023-10-10
  • 详解Go语言中Get/Post请求测试

    详解Go语言中Get/Post请求测试

    这篇文章主要为大家详细介绍了Go语言中的环境安装以及Get和Post请求接口的测试,文中的示例代码讲解详细,感兴趣的可以跟随小编一起学习一下
    2022-06-06
  • Go语言中同一个package中函数互相调用为undefined的解决

    Go语言中同一个package中函数互相调用为undefined的解决

    这篇文章主要介绍了Go语言中同一个package中函数互相调用为undefined的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • Golang中channel使用的一些小技巧

    Golang中channel使用的一些小技巧

    这篇文章主要介绍了Golang中channel使用的一些小技巧,本文讲解了关闭2次、读取的时候channel提前关闭了、向已经关闭的channel写数据等技巧及这实例代码,需要的朋友可以参考下
    2015-07-07
  • go语言实现简易比特币系统钱包的原理解析

    go语言实现简易比特币系统钱包的原理解析

    这篇文章主要介绍了go语言实现简易比特币系统钱包的原理解析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • 利用golang进行OpenCV学习和开发的步骤

    利用golang进行OpenCV学习和开发的步骤

    目前,OpenCV逐步成为一个通用的基础研究和产品开发平台,下面这篇文章主要给大家介绍了关于利用golang进行OpenCV学习和开发的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2018-09-09
  • Golang中堆排序的实现

    Golang中堆排序的实现

    堆是一棵基于数组实现的特殊的完全二叉树,本文主要介绍了Golang中堆排序的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • Go 使用os包操作环境变量的方法

    Go 使用os包操作环境变量的方法

    环境变量通常在程序启动时就已设置好,在需要的时候随时读取,Go使用简单的几个函数就可以对环境变量进行增删查改,本文给大家介绍Go 使用os包操作环境变量的方法,感兴趣的朋友跟随小编一起看看吧
    2024-07-07

最新评论