go性能优化建议

一.简介

​ 性能优化的前提是满足正确可靠、简洁清晰等质量因素。性能优化是综合评估,有时候时间效率和空间效率可能对立。针对 Go语言特性,下文介绍 Go 相关的性能优化建议。

二.性能优化建议

1.Benchmark

​ Benchmark能够为Go语言提供支持基准性能测试,能够提供实际的数据衡量。通过go test -bench=. -benchmen命令进行基准性能测试。

go 复制代码
// from fib.go
func Fib(n int) int {
    if n <2{
        return n
    }
	return Fib(n-1) + Fib(n-2)
}
// from fib_test.go
func BenchmarkFib10(b *testing.B) {
    // run the Fib function b.N times
    for n := 0; n < b.N; n++{
        Fib(10)
    }
}

结果说明:

​ 第四行第一个结果表示BenchmarkFib10是测试函数名8 表示GOMAXPROCS的值为8,第四行第二个结果表示一共执行1855870次即b.N的值,第四行第三个结果表示每次执行花费602.5ns,第四行第四个结果表示每次执行申请多大的内存,第四行第五个结果表示每次执行申请的几次内存。

2.slice

​ slice可以预分配内存,使用make() 初始化切片时提供容量信息。

go 复制代码
func NoPreAlloc( size int) {
    data := make([]int, 0)
    for k := 0; k < size; k++ {
        data = append( data, k )
    }
}


func PreAlloc(size int) {
    data := make([]int, 0, size )
    for k := 0; k < size; k++ {
        data = append( data, k )
    }
}

实验结果:

​ 切片本质是一个数组片段的描述包括数组指针,片段的长度,片段的容量(不改变内存分配情况下的最大长度)。切片操作并不复制切片指向的元素,创建一个新的切片会复用原来切片的底层数组。

3.map

​ map预分配内存

go 复制代码
func NoPreAlloc(size int) {
    data := make(map[int]int )
    for i := 0; i < size; i++ {
        data[i] = 1
    }
}

func PreAlloc(size int) {
    data := make(map[int]int, size)
    for i := 0; i < size; i++{
		data[i] = 1
    } 
}

实验结果:

不断向 map 中添加元素的操作会触发 map 的扩容,提前分配好空间可以减少内存拷贝和 Rehash 的消耗,根据实际需求提前预估好需要的空间。

4.字符串处理

​ 使用strings.Builder

go 复制代码
func ByteBuffer(n int, str string) string {
    buf := new( bytes .Buffer)
    for i := 0;i < n; i++ {
        buf.writeString(str)
    }
    return buf.String()
}

实验结果:

使用 +字符串拼接性能最差,strings.Builder,bytes.Buffer 相近,strings.Buffer 更快。字符串在 Go语言中是不可变类型,占用内存大小是固定的。使用 + 每次都会重新分配内存。strings.Builder,bytes.Buffer 底层都是 []byte 数组。内存扩容策略不需要每次拼接重新分配内存。

5.空结构体

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

go 复制代码
func EmptyStructMap(n int) {
    m := make(map[int]struct{})
    
    for i := 0;i < n; i++ {
        m[i] = struct{}{}
    }
}

func BoolMap(n int) {
    m := make(map[int]bool)
    
    for i := 0;i < n; i++ {
        m[i] = false
    }
}

实验结果:

实现 Set,可以考虑用 map 来代替。对于这个场景,只需要用到 map 的键,而不需要值。即使是将 map 的值设置为 bool 类型,也会多占据 1个字节空间。

  1. atomic

    使用atomic包

    go 复制代码
    type atomicCounter struct {
        i int32
    }
    
    func AtomicAddOne( c
    *atomicCounter) {
        atomic.AddInt32( &c.i, 1)
    }
    
    type mutexCounter struct {
        i int32
        m sync.Mutex
    }
    
    func MutexAddOne(c *mutexCounter) {
        c.m.Lock()
    	c.i+
    	c.m.Unlock()
    }

实验结果:

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

三.小结

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

相关推荐
小蜗牛慢慢爬行39 分钟前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
wm10431 小时前
java web springboot
java·spring boot·后端
龙少95433 小时前
【深入理解@EnableCaching】
java·后端·spring
溟洵5 小时前
Linux下学【MySQL】表中插入和查询的进阶操作(配实操图和SQL语句通俗易懂)
linux·运维·数据库·后端·sql·mysql
SomeB1oody7 小时前
【Rust自学】6.1. 定义枚举
开发语言·后端·rust
SomeB1oody7 小时前
【Rust自学】5.3. struct的方法(Method)
开发语言·后端·rust
啦啦右一9 小时前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
森屿Serien9 小时前
Spring Boot常用注解
java·spring boot·后端
盛派网络小助手11 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
∝请叫*我简单先生11 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl