性能优化建议

218 阅读3分钟

Benchmark使用

Go语言的基准测试(Benchmark)是一种用于测量代码性能的方法。基准测试可以帮助开发者了解特定代码段的执行效率,比如函数调用的耗时、循环的执行速度等。通过基准测试,你可以优化代码以提高程序的整体性能。

基准测试的基本结构

基准测试使用 testing 包中的 Benchmark 函数来定义。一个典型的基准测试函数看起来像这样:

import (
    "testing"
)

func BenchmarkExample(b *testing.B) {
    // 这里放置要测试的代码
    for i := 0; i < b.N; i++ {
        // 测试这段代码的性能
    }
}

在这个例子中,b.N 是一个由测试框架设置的计数器,表示测试函数应该运行的次数。测试框架会自动调整这个值,直到测试的时间足够长,以便获得准确的性能数据。

如何运行基准测试

要运行基准测试,可以使用 go test 命令加上 -bench 标志。例如,如果你有一个名为 example_test.go 的文件,其中包含了基准测试函数,你可以这样运行它:

go test -bench=.

如果你想运行特定的基准测试,可以指定基准测试函数的名字:

go test -bench=BenchmarkExample

基准测试的例子

假设我们有一个简单的函数,我们想要测试它的性能:

package main

import (
    "testing"
)

// 这是我们要测试的函数
func Add(a, b int) int {
    return a + b
}

// 基准测试函数
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

在这个例子中,BenchmarkAdd 函数会重复调用 Add 函数 b.N 次,并记录下总的执行时间。当你运行基准测试时,你会看到类似如下的输出:

BenchmarkAdd-8    1000000000               0.00 ns/op
PASS
ok      command-line-arguments  0.542s

这里的输出表明,BenchmarkAdd 函数被调用了 10 亿次,每次调用的平均时间大约是 0.00 纳秒。

Slice

slice预分配内存

尽可能在使用make()初始化切片时提供容量信息

Map

map预分配内存

分析:

不断向map中添加元素的操作会触发map扩容

提前分配好空间可以减少内存拷贝和Rehash的消耗

建议根据实际需求提前预估好需要的空间

字符串处理

使用strings.Builder 使用 + 拼接性能最差, strings.Builder, bytes.Buffer相近,strings.Buffer 更快

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

空结构体

使用空结构体节省内存

空结构体struct{}实例不占据任何的内存空间

可作为各种场景下的占位符使用

节省资源

空结构体本身具备很强的语义,即这里不需要任何值,仅作为占位符

示例

作为通道的消息类型

当通道用于信号传递,而不需要传递任何数据时,可以使用空结构体。

func main() { 
// 创建一个通道,通道的消息类型为空结构体 
ch := make(chan struct{})
// 启动一个goroutine,模拟长时间运行的任务 
go func() { 
time.Sleep(2 * time.Second) 
close(ch) // 任务完成,关闭通道 
}() 
// 等待任务完成
<-ch fmt.Println("Task completed") 
}

atomic包

使用atomic包 锁的实现是通过硬件实现,属于系统调用

atomic操作是通过硬件实现,效率比锁高

sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量

对于非数值操作,可以使用atomic.Value,能承载一个interface{}