Go性能优化小技巧_Go_jinjin_InfoQ写作平台
source link: https://xie.infoq.cn/article/381a86777d463deddd50b25b9
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Go 性能优化小技巧
- 2022 年 3 月 23 日
本文字数:2188 字
阅读完需:约 7 分钟
当开发一个新的项目时,由于访问量级比较少,对于程序的性能来说不是太过重要。当随着业务的迭代升级,通过增加服务器来支撑业务,如果一个 server 程序用了公有云 1000 台服务器,而且都是大型机器,成本上升就不是一个级别。为此总结最近优化的一些小技巧来提升 GO 程序的性能,毕竟能减少几台是几台,都是钱。
而在优化的过程中,看了一下代码,切片用的地方还真不少,但性能却不高,为此总结一下切片的优化过程:
slice 赋值比 append 性能优:
在使用 slice 时,如果知道切片的容量与大小,可以进行赋值操作,相比 append 减少返回新的切片
func BenchmarkForA(b *testing.B) {
list := make([]*int, 0)
for i := 0; i < 100; i++ {
a := i
list = append(list, &a)
}
b.ResetTimer()
for j := 0; j < b.N; j++ {
newList := make([]*int, 100, 100)
for i, v := range list {
newList[i] = v
}
}
b.StopTimer()
}
func BenchmarkForB(b *testing.B) {
list := make([]*int, 0)
for i := 0; i < 100; i++ {
a := i
list = append(list, &a)
}
b.ResetTimer()
for j := 0; j < b.N; j++ {
newList := make([]*int, 0, 100)
for _, v := range list {
newList = append(newList, v)
}
}
b.StopTimer()
}
同样是给一个切片添加元素,赋值操作性能提升 35%
再看一下 append 的源码,返回的是一个新的切片:
给 slice 扩容
当如果两个 slice 合并时,可以用 copy 减少内存分配,提高性能
func BenchmarkAppendA(b *testing.B) {
list := make([]*int, 0)
for i := 0; i < 100; i++ {
a := i
list = append(list, &a)
}
b.ResetTimer()
for j := 0; j < b.N; j++ {
existList := make([]*int, 0)
for i := 1; i <= 5; i++ {
a := i + 100
existList = append(existList, &a)
}
for _, v := range list {
existList = append(existList, v)
}
}
b.StopTimer()
}
func BenchmarkAppendB(b *testing.B) {
list := make([]*int, 0)
for i := 0; i < 100; i++ {
a := i
list = append(list, &a)
}
b.ResetTimer()
for j := 0; j < b.N; j++ {
existList := make([]*int, 0)
for i := 1; i <= 5; i++ {
a := i + 100
existList = append(existList, &a)
}
newList := make([]*int, 105)
copy(newList, existList)
copyList := make([]*int, 0, 100)
for _, v := range list {
copyList = append(copyList, v)
}
copy(newList[len(existList):], copyList)
existList = newList
}
b.StopTimer()
}
要给一个切片扩容,但很多同学都就会用 append,其实用 copy 性能可提升 50%:
byte to string
很多时候我们都需要网络服务加载一些内容,返回结果都是 byte 类型,经常会把 byte 转换 string,但是 string 操作会增加一次 copy 操作,因此我们可以通过 unsafe.pointer 进行转换
func unsafeToString(bytes []byte) *string {
hdr := &reflect.StringHeader{
Data: uintptr(unsafe.Pointer(&bytes[0])),
Len: len(bytes),
}
return (*string)(unsafe.Pointer(hdr))
}
func BenchmarkByteToStringA(b *testing.B) {
b.ResetTimer()
for j := 0; j < b.N; j++ {
aa := []byte("hello world")
str := unsafeToString(aa)
fmt.Sprintf("%v", *str)
}
b.StopTimer()
}
func BenchmarkByteToStringB(b *testing.B) {
b.ResetTimer()
for j := 0; j < b.N; j++ {
aa := []byte("hello world")
str := string(aa)
fmt.Sprintf("%v", str)
}
b.StopTimer()
}
如果 byte 的内容较大时,优化效果明显:
但这里值得注意的是,string 类型是不可以修改的,而 byte 是可以修改的,所以这时对底层数组的值进行修改,将会造成严重的错误
GC 优化
在优化的过程中,看到好多 scanObject,findObject 占用 CPU,这是因为常驻于内存中结构体指针的数目太大了,所以减小垃圾回收压力的一个方法就是减少常驻于内存的结构体指针。
然而优化前的程序 slice 元素几乎用的都是指针,指针又指向一个结构体,指针虽小但每次都增加堆的分配,再看看以下的例子:
type AInt32 struct {
TemplateType int32
DataStr dataStr
}
type dataStr struct {
numStr string
}
func BenchmarkSliceA(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
list := make([]AInt32, 10000, 10000)
for j := 0; j < 10000; j++ {
a := AInt32{
TemplateType: int32(j),
DataStr: dataStr{
numStr: strconv.Itoa(i),
},
}
list[i] = a
}
}
b.StopTimer()
}
func BenchmarkSliceB(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
list := make([]*AInt32, 10000, 10000)
for j := 0; j < 10000; j++ {
a := AInt32{
TemplateType: int32(j),
DataStr: dataStr{
numStr: strconv.Itoa(i),
},
}
list[i] = &a
}
}
b.StopTimer()
}
输出结果:
SliceA 明显优于 SliceB 的例子,这就是问题所在,产生的指针太多,导致 GC 不断的查询,添加标记 CPU 一直居高不下。
注意,但这方法不是万能,如果结构过大,还是建议用指针,防止拷贝大内存。
总结
性能优化是修炼很好的一个经历,通过这次尝试,真的是收获良多,对切片与 GC 有了更深的理解,希望以上的少少技巧能帮助大家对性能优化有所帮助。
Recommend
-
13
图解 HTTP 权威指南(一)| HTTP 报文运维汪关注发布于: 2020 年 12 月 22 日
-
8
InfoQ 写作平台版权声明: 本文为 InfoQ 作者【undefined】的原创文章。原文链接:【
-
5
刚开始接触技术文章写作的时候,是完全不知道怎么写的状态,没有任何的技术文章写作经验,完全都是随心所欲地写笔记类型的技术文章。讲真的,提起写作,我就不得不说我当初是怎么加入技术文章创作的行列来的。写作懵懂期...
-
5
之前有尝试在 SparkSQL 内核添加自定义 SQL 操作不同的底层数据源,实现计算分析任务,这里就对 SparkSQL 的 Catalyst 模块进行简要的分析。在早期大数据时代的大规模处理数据的技术是 Hadoop 提供的 MapReduce 任务,但这种框架执行效率太慢,进行一些关...
-
5
Java 并发编程系列第二篇Synchronized,文章风格依然是图文并茂,通俗易懂,本文带读者们由浅入深理解Synchronized,让读者们也能与面试官疯狂对线。在并发编程中Synchronized一直都是元老级的角色,Jdk 1...
-
5
Cassandra 的调优总结发布于: 2021 年 07 月 23 日Cassandra 的吞吐量随着更多的 CPU 内核、更多的 RAM 和更快的磁盘而提高,虽然 Cassandra 可以在测试或开发环境的小型服务器上运行,但是线上的生成环境至少需要 2 个...
-
3
Rust 从 0 到 1- 高级特性 -Traits 进阶山关注发布于: 2021 年 08 月 25 日 在前面那的章节我们已经介绍过 trait,但...
-
8
后起之秀 -network policy 之 eBPF 实现Lance关注发布于: 2021 年 09 月 29 日这篇是 Network Policy 最后一篇,主题是...
-
4
一文教你全方位揭秘 Ajax 指南
-
7
Zookeeper 是一个分布式应用程序的分布式开源协调服务。是 Apache Hadoop 的一个子项目,主要用来解决分布式应用中经常遇到的一些数据管理问题,例如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。Zookeeper 工作原理Zoo...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK