使用runtime和unsafe做性能评估
1 使用runtime和unsafe对比资源使用率。
大多数场景,Go核心团队自用的机制,在runtime, reflect, sync, syscall都有广泛的使用。
它支持程序员在go的安全类型中操作内存。
想要使用unsafe包,就必须遵循 unsafe.Pointer的安全使用规则:
1 在常规操作下,go类型是安全的,但是使用unsafe包可以绕过这些类型安全的保护。
2 go兼容性不包括unsafe的任何承诺,非必要不要使用 unsafe包,尤其是 unsafe.Pointer
3 uintptr 仅仅是一个整型值,即便它存储的是 内存对象的地址值,它对内存对象也起不到引用作用。
4 使用unsafe包,请先牢记和理解 unsafe.Pointer 六个安全使用模式。
5 使用了unsafe包,请使用 go vet 对代码进行 合规检查
c语言是静态语言,但不是类型安全的。 所谓类型安全是一块内存数据一旦被特定类型解释,该内存数据与该类型变量建立关联。
但是在c语言中,可以把 int 类型,使用c代码通过内存地址的置换,解释为字符串类型。
- runtime包
Go 语言类型安全是建立在Go编译器的静态检查以及Go运行时 利用类型信息进行的运行时检查的。
在语法层面,为了实现常规类型安全,Go语言做了诸多限制.
而runtime包在上一节有详细内容介绍,它支持在运行时分析程序。
返回的内存分配器统计信息是在调用ReadMemStats时最新的。
调用ReadMemStats。这是与堆统计不同的。
它是最近完成的垃圾收集周期的一个快照。收集周期的快照。
runtime metrics 指标
metrics 提供了一个稳定的接口来访问实现定义的度量的稳定接口。
这个包类似于现有的函数如runtime.ReadMemStats和debug.ReadGCStats,但明显更通用。
这个包所定义的指标集可以随着运行时本身的发展而变化变化,也使得不同的Go实现有不同的变化,其相关的度量集可能不会相交。
以pprof可视化工具期望的格式写入运行时剖析数据。以格式化写入运行时剖析数据,并由 pprof 可视化工具进行处理。
- 解析一个go程序
剖析Go程序的第一步是启用剖析功能。 支持对用标准测试构建的基准进行剖析。
此包内置在go test中。例如,下面的命令 在当前目录下运行基准,并将CPU和 内存配置文件到 cpu.prof 和 mem.prof。
go test -cpuprofile cpu.prof -memprofile mem.prof -bench .
也有一个标准的HTTP接口来获取剖析数据。添加 以下一行将在/debug/pprof/下安装处理程序。
2 转换效率的对比实例
首先我们需要知道需要做什么对比,在go中转换类型的内置默认方法有强制转换如
var i int = 64
fmt.Printf("type:%T,value:%v \n",string(i), string(i)) //显示 64对应的字符
输出: type:string,value:"@"
us := strconv.FormatInt(int64(i), 10)
fmt.Printf("type:%T,value:%v \n",string(us), string(us)) //显示64对应的十进制数字
输出: typeus:string,value:"64"
我们实现go语言的类型string和[]byte之间的转换,和 基于 unsafe的类型转换,并进行benchmark基准性能测试。
而字节码到字符串的转换 byte to string 更消耗性能,
string类型变量是不可变类型,而[]byte为可变类型,当转换为[]byte时,go需要从系统申请一块新内存,
并将string类型变量值赋予这个新的内存,通过下面基于unsafe包的实现,不需要额外的内存复制:
转换后的[]byte变量与输入参数的string类型变量共享底层存储(并不能修改返回的切片就能改变原字符串。)
而将[]byte转换为string则简单一些,因为[]byte内部时一个三元组ptr,len,cap, 在string内部为 ptr,len。
只需要通过Pointer将[]byte内部重新解释为string的内部表示。
在这里,只是把 “this is golang!” 这字符串转string换为 []byte 或 把 []byte转换为字符串 string.
//benchChange.go
package main
var benchStr string = "this is golang!"
使用go语言内置转换方式
func B2String(b []byte) string {
return string(b)
}
func S2Bytes(s string) []byte {
return []byte(s)
}
func BenchmarkS2Bytes(b *testing.B) {
var sbe string = benchStr
for n := 0; n < b.N; n++ {
S2Bytes(sbe)
}
}
func BenchmarkB2String(b *testing.B) {
var sbe []byte = []byte(benchStr)
for n := 0; n < b.N; n++ {
B2String(sbe)
}
}
使用unsafe避免内存分配和重新复制。
bytes转换为string
func Bytes2String(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
func BenchmarkBytes2String(b *testing.B) {
var sbe []byte = []byte(benchStr)
for n := 0; n < b.N; n++ {
Bytes2String(sbe)
}
}
string转换为bytes
func String2Bytes(s string) []byte {
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len,
}
return *(*[]byte)(unsafe.Pointer(&bh))
}
func BenchmarkString2Bytes(b *testing.B) {
var sbe string = benchStr
for n := 0; n < b.N; n++ {
String2Bytes(sbe)
}
}
将以上代码保存为 _unsafe_case.go。
使用go test执行case并且记录对应函数使用的cpu资源和内存mem资源。
go test -cpuprofile cpu.prof -memprofile mem.prof -bench ./_unsafe_case.go
-
效率对比: 内存使用数据
go tool pprof cpu.prof
排名前200条使用记录:
top 200 -cum
Showing nodes accounting for 3640.63kB, 100% of 3640.63kB total
flat flat% sum% cum cum%
0 0% 0% 1928.19kB 52.96% runtime/pprof.(*profileBuilder).build
0 0% 0% 1928.19kB 52.96% runtime/pprof.profileWriter
0 0% 0% 1712.44kB 47.04% main.main
0 0% 0% 1712.44kB 47.04% runtime.main
0 0% 0% 1712.44kB 47.04% testing.(*M).Run
0 0% 0% 1414.69kB 38.86% runtime/pprof.(*profileBuilder).appendLocsForStack
512.10kB 14.07% 14.07% 1414.69kB 38.86% runtime/pprof.(*profileBuilder).emitLocation
1184.27kB 32.53% 46.60% 1184.27kB 32.53% runtime/pprof.StartCPUProfile
0 0% 46.60% 1184.27kB 32.53% testing.(*M).before
0 0% 46.60% 1184.27kB 32.53% testing/internal/testdeps.TestDeps.StartCPUProfile
902.59kB 24.79% 71.39% 902.59kB 24.79% compress/flate.NewWriter
0 0% 71.39% 902.59kB 24.79% compress/gzip.(*Writer).Write
0 0% 71.39% 902.59kB 24.79% runtime/pprof.(*profileBuilder).flush
0 0% 71.39% 528.17kB 14.51% regexp.(*Regexp).MatchString (inline)
0 0% 71.39% 528.17kB 14.51% regexp.(*Regexp).backtrack
0 0% 71.39% 528.17kB 14.51% regexp.(*Regexp).doExecute
0 0% 71.39% 528.17kB 14.51% regexp.(*Regexp).doMatch (inline)
528.17kB 14.51% 85.90% 528.17kB 14.51% regexp.(*bitState).reset
0 0% 85.90% 528.17kB 14.51% testing.newMatcher
0 0% 85.90% 528.17kB 14.51% testing.runBenchmarks
0 0% 85.90% 528.17kB 14.51% testing.simpleMatch.verify
0 0% 85.90% 528.17kB 14.51% testing/internal/testdeps.TestDeps.MatchString
0 0% 85.90% 513.50kB 14.10% runtime/pprof.(*profileBuilder).pbSample
0 0% 85.90% 513.50kB 14.10% runtime/pprof.(*protobuf).length (inline)
0 0% 85.90% 513.50kB 14.10% runtime/pprof.(*protobuf).uint64s
513.50kB 14.10% 100% 513.50kB 14.10% runtime/pprof.(*protobuf).varint (inline)
可以看出,使用go语言层面的转换,cpu使用率有大幅提高。 内存使用率没有上榜不需对比。
根据生成的性能数据查看函数的资源使用率
go tool pprof cpu.prof
- 效率对比: cpu使用数据
排名前20的cpu资源使用率:
top 20 -cum
Showing nodes accounting for 4.01s, 97.33% of 4.12s total
Dropped 52 nodes (cum <= 0.02s)
flat flat% sum% cum cum%
0 0% 0% 4.04s 98.06% testing.(*B).runN
0 0% 0% 4.03s 97.82% testing.(*B).launch
0.31s 7.52% 7.52% 1.27s 30.83% _unsafe_case.BenchmarkS2Bytes
0.35s 8.50% 16.02% 1.15s 27.91% _unsafe_case.BenchmarkB2String
0.14s 3.40% 19.42% 0.94s 22.82% _unsafe_case.S2Bytes (inline)
0.08s 1.94% 21.36% 0.80s 19.42% _unsafe_case.B2String (inline)
0.48s 11.65% 33.01% 0.80s 19.42% runtime.stringtoslicebyte
0.36s 8.74% 41.75% 0.72s 17.48% runtime.slicebytetostring
0.68s 16.50% 58.25% 0.68s 16.50% runtime.memmove
0.51s 12.38% 70.63% 0.51s 12.38% _unsafe_case.BenchmarkBytes2String
0.31s 7.52% 78.16% 0.31s 7.52% _unsafe_case.BenchmarkGoFloat64bits
0.27s 6.55% 84.71% 0.27s 6.55% _unsafe_case.BenchmarkGouint64bits
0.27s 6.55% 91.26% 0.27s 6.55% _unsafe_case.BenchmarkString2Bytes
0.25s 6.07% 97.33% 0.25s 6.07% _unsafe_case.BenchmarkFloat64bits
0 0% 97.33% 0.03s 0.73% runtime.mcall
可以看出 调用内置强制转换函数 S2Bytes 的 BenchmarkS2Bytes 使用了30%cpu
而调用内置强制转换函数 的 BenchmarkB2String 使用了 27%
而使用unsafe的转换方式,平均为 7%,至少提升了4倍转换效率。约为其内置强制转换方式的 26%.
3 小结
下一节我们介绍另一个优化性能的例子,获取10亿内的回文平方根数字时提升性能。
参考文档:
arxiv.org/abs/2006.09973
https://golang.org/pkg/runtime/debug/#SetTraceback.
- 点赞
- 收藏
- 关注作者
评论(0)