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。

三.小结

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

相关推荐
2401_857622661 小时前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
2402_857589361 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
哎呦没3 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
_.Switch3 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
杨哥带你写代码4 小时前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端
AskHarries5 小时前
读《show your work》的一点感悟
后端
A尘埃5 小时前
SpringBoot的数据访问
java·spring boot·后端
yang-23075 小时前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端
Marst Code5 小时前
(Django)初步使用
后端·python·django
代码之光_19805 小时前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端