• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Go 性能优化

武飞扬头像
等你呦
帮助2

前言

本文将针对Go语言特性介绍Go相关的性能优化建议、性能优化的原则和流程、常用的Go语言程序优化手段。

简介

性能优化的前提是满足正确可靠、简洁清晰等质量因素;性能优化是综合评估,有时候时间效率和空间效率可能对立。

Benchmark

性能表现需要实际数据衡量,Go语言提供了支持基准性能测试的工具——benchmark。

使用示例:

package service
import (
   "fmt"
   "testing"
)

func Fib(n int) {
   fmt.Println(n)
}
func BenchmarkFib(t *testing.B) {
   Fib(2)

}

执行命令:go test -bench=. -benchmem,输出结果如下:

2
goos: windows
goarch: amd64
pkg: github.com/goTouch/TicTok_SimpleVersion/service
cpu: 12th Gen Intel(R) Core(TM) i5-12500H
BenchmarkFib-16         2
2
2
2
2
1000000000               0.0005499 ns/op               0 B/op          0 allocs/op
PASS
ok      github.com/goTouch/TicTok_SimpleVersion/service 0.891s

结果说明:

  • BenchmarkFib-16为测试函数名,数字16表示GOMAXPROCS的值,代表CPU的核数。
  • 1000000000 表示一共执行了1000000000 9次。
  • 0.0005499 ns/op表示每次执行花费0.0005499 ns/op ns。
  • 0 B/op表示每次执行申请多大的内存。
  • 0 allocs/op表示每次执行申请了几次内存。

Slice

在使用slice时,预分配内存,尽可能在使用make()初始化切片时提供容量信息,减少后续内存分配次数。

slice预分配内存和不预分配内存的对比测试如下:

学新通

运行测试输出结果如下:

shell
复制代码
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.70GHz
BenchmarkNoPreAlloc-8    5835811    193.2 ns/op    248 B/op    5 allocs/op
BenchmarkPreAlloc-8    26007894    40.22 ns/op    80 B/op    1 allocs/op
PASS
ok      command-line-arguments  2.849s

从输出的结果来看,分配内存的代码效率更高。为什么有这样的差异呢?

切片本质是一个数组片段的描述包括:数组指针、片段的长度、片段的容量。切片操作并不是复制切片执行的元素,他会创建一个新的切片会复用原来切片的底层数组,会涉及到扩容操作,如果预分配好了数组长度,就会减少扩容的次数,从而提高效率。

slice陷阱

slice存在一个陷阱:大内存未释放。

在已有切片基础上创建切片,不会创建新的底层数组。

场景:

  • 原切片较大,代码在原切片基础上新建小切片
  • 原底层数组在内存中有运用得不到释放

针对这种场景,可以使用copy替代re-slice

Map

在使用map时,也可以预分配内存,来提升效率。

下面通过基准测试来看下,使用预分配内存和不使用预分配内存的性能区别:

学新通

运行测试,输出结果如下:

shell
复制代码
BenchmarkNoPreAlloc-8    11716    108794 ns/op    86552 B/op    64 allocs/op
BenchmarkPreAlloc-8    29910    39307 ns/op    41097 B/op    6 allocs/op

出现性能差异的原因是什么呢?因为在不断向map中添加元素的操作会触发map的扩容,而提前分配好空间可以减少内存拷贝和Rehash的消耗。

所以在使用map的时候,建议根据实际需求提前预估好需要的内存。

字符串处理

字符串拼接有很多种方法,常见的有直接加号拼接、strings.Builderbytes.Buffer,下面通过基准测试对比下3者的性能区别:

学新通

学新通

运行输出结果如下:

shell
复制代码
BenchmarkPlus-8    2569    471758 ns/op    1602938 B/op    999 allocs/op
BenchmarkStrBuilder-8    267066    4466 ns/op    8440 B/op    11 allocs/op
BenchmarkByteBuffer-8    122371    8971 ns/op    11200 B/op    8 allocs/op

可以看出,strings.Builder的性能最好,bytes.Buffer的性能次之,直接拼接性能最差。

原因分析:

  • 字符串在Go语言中是不可变类型,占用内存大小是固定的
  • 使用 拼接每次都会重新分配内存
  • strings.Builderbytes.Buffer底层都是[]byte数组
  • 内存扩容策略,不需要每次拼接重新分配内存

预分配

结合slicemapstrings.Builderbytes.Buffer也可以进行预分配,下面来测试下使用预分配和不使用预分配的性能区别:

学新通

运行测试,输出结果如下:

shell
复制代码
BenchmarkStrBuilder-8    286748    4252 ns/op    8440 B/op    11 allocs/op
BenchmarkByteBuffer-8    90979    11179 ns/op    11200 B/op    8 allocs/op
BenchmarkPreStrBuilder-8    183267    7690 ns/op    3072 B/op    1 allocs/op
BenchmarkPreByteBuffer-8    156591    7017 ns/op    6144 B/op    2 allocs/op

看一看到,使用预分配后,性能又会进一步的提升。

空结构体

性能优化有时是时间和空间的平衡,之前提到的都是提高时间效率的点,对于空间上是否有优化的手段呢? 空结构体是节省内存空间的一个手段。

使用空结构体节省内存,空结构体struct{}实例不占据任何内存空间,可作为各种场景下的占位符使用,可以节省资源,空结构体本身具备很强的语义,即这里不需要任何值,仅作为占位符。

下面来对比下,map存储使用空结构体和使用bool在内存空间上的区别。

学新通 运行测试,输出结果如下:

BenchmarkEmptyStructMap-8   15211   78213 ns/op   47734 B/op   65 allocs/op
BenchmarkBoolMap-8   14697   81723 ns/op   53308 B/op   72 allocs/op

从运行结果上来看,使用空结构体确实能节省一些空间。

空结构体struct{}的一个典型的实现场景是实现Set结构,Set只需用到map的键,不需要值,而空结构体不占用空间,即使是将map的值设置为bool类型,也会比空结构体多占据1个字节。

开源Set结构实现:github.com/deckarep/go…

atomic

在实际编程中经常会用到多线程编程,在Go中有atomic包,在这个包中会维护一个原子的变量,对值进行操作。或者通过加锁的方式操作。

下面通过示例来看下使用atomic包和加锁的方式,对比一下两者的性能:

学新通

运行测试,输出结果如下:

BenchmarkAtomicAddOne-8   55781104   18.17 ns/op   4 B/op   1 allocs/op
BenchmarkMutexAddOne-8   31495927   36.35 ns/op   16 B/op   1 allocs/op

从测试结果可以看出,使用atomic包的性能要比加锁的方式高。

原因分析:锁的实现是通过操作系统来实现的,属于系统调用;而atomic操作是通过硬件实现,效率比锁高;sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量;对于非数值操作,可以使用atomic.Value,能承载一个interface{}

总结

对于性能优化,需要注意以下几点:

  • 避免常见的性能陷阱可以保证大部分程序的性能
  • 普通应用代码,不要一味地追求程序的性能
  • 越高级的性能优化手段越容易出现问题
  • 在满足正确可靠、简洁清晰的质量要求的前提下提高程序性能

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbcaac
系列文章
更多 icon
同类精品
更多 icon
继续加载