Go高性能编程阅读笔记
题记
本文主要记录Go语言高性能编程 的一书中的笔记(有些笔记也是抄别人的)。
benchmark
- 进行性能测试时,尽可能保持测试环境的稳定
- 实现 benchmark 测试
• 位于_test.go
文件中
• 函数名以Benchmark
开头
• 参数为b *testing.B
•b.ResetTimer()
可重置定时器
•b.StopTimer()
暂停计时
•b.StartTimer()
开始计时 - 执行 benchmark 测试
•go test -bench .
执行当前测试
•b.N
决定用例需要执行的次数
•-bench
可传入正则,匹配用例
•-cpu
可改变 CPU 核数
•-benchtime
可指定执行时间或具体次数
•-count
可设置 benchmark 轮数
•-benchmem
可查看内存分配量和分配次数
pprof
性能分析类型
- CPU 性能分析,runtime 每隔 10 ms 中断一次,记录此时正在运行的 goroutines 的堆栈信息
- 内存性能分析,记录堆内存分配时的堆栈信息,忽略栈内存分配信息,默认每 1000 次采样 1 次
- 阻塞性能分析,GO 中独有的,记录一个协程等待一个共享资源花费的时间
- 锁性能分析,记录因为锁竞争导致的等待或延时
CPU 性能分析
使用原生
runtime/pprof
包,通过在 main 函数中添加代码运行可生成性能分析报告:1
2pprof.StartCPUProfile(os.Stdout)
defer pprof.StopCPUProfile()可通过
go tool pprof -http=:9999 cpu.pprof
在 web 页面查看分析数据可通过
go tool pprof cpu.prof
交互模式查看分析数据,可使用help
查看支持的命令和选项
内存性能分析
使用
pkg/profile
库,通过在 main 函数中添加代码运行可生成性能分析报告:1
defer profile.Start(profile.MemProfile, profile.MemProfileRate(1)).Stop()
同样可通过 web 页面或交互模式查看分析数据
benchmark 生成 profile
- 可通过在
go test
中添加参数-cpuprofile=$FILE,-memprofile=$FILE,-blockprofile=$FILE
生成相应的 profile 文件 - 生成的 profile 文件同样可通过 web 页面或交互模式查看分析数据
字符串拼接
- 字符串最高效的拼接方式是结合预分配内存方式
Grow
使用string.Builder
- 当使用
+
拼接字符串时,生成新字符串,需要开辟新的空间 - 当使用
strings.Builder
,bytes.Buffer
或[]byte
的内存是按倍数申请的,在原基础上不断增加 strings.Builder
比bytes.Buffer
性能更快,一个重要区别在于bytes.Buffer
转化为字符串重新申请了一块空间存放生成的字符串变量;而strings.Builder
直接将底层的[]byte
转换成字符串类型返回
切片性能及陷阱
GO 中的数组变量属于值类型,当数组变量被赋值或传递时,实际上会复制整个数组
切片本质是数组片段的描述,包括数组的指针,片段的长度和容量,切片操作并不复制切片指向的元素,而是复用原来切片的底层数组
长度是切片实际拥有的元素,使用
len
可得到切片长度容量是切片预分配的内存能够容纳的元素个数,使用
1
cap
可得到切片容量
- 当 append 之后的元素小于等于 cap,将会直接利用底层元素剩余的空间
- 当 append 后的元素大于 cap,将会分配一块更大的区域来容纳新的底层数组,在容量较小的时候,通常是以 2 的倍数扩大
可能存在只使用了一小段切片,但是底层数组仍被占用,得不到使用,推荐使用
copy
替代默认的re-slice
for 和 range 的性能比较
- range的数据是slice,仅会计算一次,如果在循环中修改切片的长度不会改变本次循环的次数。
- 和切片不同的是,迭代过程中,删除还未迭代到的键值对,则该键值对不会被迭代。
- range 在迭代过程中返回的是迭代值的拷贝,如果每次迭代的元素的内存占用很低,那么 for 和 range 的性能几乎是一样,例如
[]int
。但是如果迭代的元素内存占用较高,例如一个包含很多属性的 struct 结构体,那么 for 的性能将显著地高于 range,有时候甚至会有上千倍的性能差异。
Go Reflect 提高反射性能
避免使用反射
Go 空结构体 struct{} 的使用
空结构体占用空间吗?
1
unsafe.Sizeof(struct{}{}) //结果为0
空结构体的作用
- 实现集合(Set)
- 不发送数据的信道(channel)
- 仅包含方法的结构体
Go struct 内存对齐
1 | type Args struct { |
合理的内存对齐可以提高内存读写的性能,并且便于实现变量操作的原子性。
读写锁和互斥锁的性能比较
- 读写比为 9:1 时,读写锁的性能约为互斥锁的 8 倍
- 读写比为 1:9 时,读写锁性能相当
- 读写比为 5:5 时,读写锁的性能约为互斥锁的 2 倍
如何退出协程 goroutine (超时场景)
如何退出协程 goroutine (其他场景)
控制协程(goroutine)的并发数量
并发过高导致程序崩溃,如何控制并发数量:
- 利用 channel 的缓存区(chan struct{})
- 利用第三方库
- 调整系统资源的上限
- ulimit(ulimit -a查看系统当前的设置, ulimit -n ** 调整同时打开的文件句柄数量)
- 虚拟内存(virtual memory)
Go sync.Pool
1 | type Student struct { |
Go sync.Once
1 | type Once struct { |
为什么将 done 置为 Once 的第一个字段:done 在热路径中,done 放在第一个字段,能够减少 CPU 指令,也就是说,这样做能够提升性能。
简单解释下这句话:
- 热路径(hot path)是程序非常频繁执行的一系列指令,sync.Once 绝大部分场景都会访问
o.done
,在热路径上是比较好理解的,如果 hot path 编译后的机器码指令更少,更直接,必然是能够提升性能的。 - 为什么放在第一个字段就能够减少指令呢?因为结构体第一个字段的地址和结构体的指针是相同的,如果是第一个字段,直接对结构体的指针解引用即可。如果是其他的字段,除了结构体指针外,还需要计算与第一个值的偏移(calculate offset)。在机器码中,偏移量是随指令传递的附加值,CPU 需要做一次偏移值与指针的加法运算,才能获取要访问的值的地址。因为,访问第一个字段的机器代码更紧凑,速度更快。
Go sync.Cond
1 | var done = false |
减小 Go 代码编译后的二进制体积
1 | go build -ldflags="-s -w" -o server main.go && upx -9 server |
Go 编译器默认编译出来的程序会带有符号表和调试信息,一般来说 release 版本可以去除调试信息以减小二进制体积。
- -s:忽略符号表和调试信息。
- -w:忽略DWARFv3调试信息,使用该选项后将无法使用gdb进行调试。
upx 有很多参数,最重要的则是压缩率,1-9
,1
代表最低压缩率,9
代表最高压缩率。upx 压缩后的程序和压缩前的程序一样,无需解压仍然能够正常地运行,这种压缩方法称之为带壳压缩,压缩包含两个部分:
- 在程序开头或其他合适的地方插入解压代码;
- 将程序的其他部分压缩。
执行时,也包含两个部分:
- 首先执行的是程序开头的插入的解压代码,将原来的程序在内存中解压出来;
- 再执行解压后的程序。
也就是说,upx 在程序执行时,会有额外的解压动作,不过这个耗时几乎可以忽略。如果对编译后的体积没什么要求的情况下,可以不使用 upx 来压缩。一般在服务器端独立运行的后台服务,无需压缩体积。
Go 逃逸分析
传值会拷贝整个对象,而传指针只会拷贝指针地址,指向的对象是同一个。传指针可以减少值的拷贝,但是会导致内存分配逃逸到堆中,增加垃圾回收(GC)的负担。在对象频繁创建和删除的场景下,传递指针导致的 GC 开销可能会严重影响性能。
一般情况下,对于需要修改原对象值,或占用内存比较大的结构体,选择传指针。对于只读的占用内存较小的结构体,直接传值能够获得更好的性能。
Go 死码消除与调试(debug)模式
死码消除(dead code elimination, DCE)是一种编译器优化技术,用处是在编译阶段去掉对程序运行结果没有任何影响的代码。死码消除有很多好处:减小程序体积,程序运行过程中避免执行无用的指令,缩短运行时间。
- 使用常量提升性能
- 可推断的局部变量
- 调试(debug)模式
- 条件编译
1 | // +build debug 表示 build tags 中包含 debug 时,该源文件参与编译。 |
- 标题: Go高性能编程阅读笔记
- 作者: Olivia的小跟班
- 创建于 : 2024-11-18 21:13:52
- 更新于 : 2024-11-19 04:42:47
- 链接: https://www.youandgentleness.cn/2024/11/18/Go高性能编程阅读笔记/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。