go - 解释预分配 slice 的基准

标签 go slice

我一直在尝试通过 make 了解 slice 预分配以及为什么这是个好主意。我注意到预分配 slice 并附加到它与仅使用 0 长度/容量初始化它然后附加到它之间存在很大的性能差异。我写了一组非常简单的基准测试:

import "testing"

func BenchmarkNoPreallocate(b *testing.B) {
    for i := 0; i < b.N; i++ {
        // Don't preallocate our initial slice
        init := []int64{}
        init = append(init, 5)
    }
}

func BenchmarkPreallocate(b *testing.B) {
    for i := 0; i < b.N; i++ {
        // Preallocate our initial slice
        init := make([]int64, 0, 1)
        init = append(init, 5)
    }
}

对结果有点疑惑:

$ go test -bench=. -benchmem
goos: linux
goarch: amd64
BenchmarkNoPreallocate-4    30000000            41.8 ns/op         8 B/op          1 allocs/op
BenchmarkPreallocate-4      2000000000           0.29 ns/op        0 B/op          0 allocs/op

我有几个问题:

  • 为什么在预分配基准案例中没有分配(它显示 0 allocs/op)?当然,我们正在预分配,但分配必须在某个时间点发生。
  • 我想这在第一个问题得到回答后可能会变得更清楚,但是预分配案例怎么会快得多?我是否误解了这个基准?

如有不明之处请告知。谢谢!

最佳答案

Go 有一个优化编译器。常量在编译时求值。变量在运行时进行评估。常量值可用于优化编译器生成的代码。例如,

package main

import "testing"

func BenchmarkNoPreallocate(b *testing.B) {
    for i := 0; i < b.N; i++ {
        // Don't preallocate our initial slice
        init := []int64{}
        init = append(init, 5)
    }
}

func BenchmarkPreallocateConst(b *testing.B) {
    const (
        l = 0
        c = 1
    )
    for i := 0; i < b.N; i++ {
        // Preallocate our initial slice
        init := make([]int64, l, c)
        init = append(init, 5)
    }
}

func BenchmarkPreallocateVar(b *testing.B) {
    var (
        l = 0
        c = 1
    )
    for i := 0; i < b.N; i++ {
        // Preallocate our initial slice
        init := make([]int64, l, c)
        init = append(init, 5)
    }
}

输出:

$ go test alloc_test.go -bench=. -benchmem
BenchmarkNoPreallocate-4         50000000    39.3 ns/op     8 B/op    1 allocs/op
BenchmarkPreallocateConst-4    2000000000     0.36 ns/op    0 B/op    0 allocs/op
BenchmarkPreallocateVar-4        50000000    28.2 ns/op     8 B/op    1 allocs/op

另一组有趣的基准:

package main

import "testing"

func BenchmarkNoPreallocate(b *testing.B) {
    const (
        l = 0
        c = 8 * 1024
    )
    for i := 0; i < b.N; i++ {
        // Don't preallocate our initial slice
        init := []int64{}
        for j := 0; j < c; j++ {
            init = append(init, 42)
        }
    }
}

func BenchmarkPreallocateConst(b *testing.B) {
    const (
        l = 0
        c = 8 * 1024
    )
    for i := 0; i < b.N; i++ {
        // Preallocate our initial slice
        init := make([]int64, l, c)
        for j := 0; j < cap(init); j++ {
            init = append(init, 42)
        }
    }
}

func BenchmarkPreallocateVar(b *testing.B) {
    var (
        l = 0
        c = 8 * 1024
    )
    for i := 0; i < b.N; i++ {
        // Preallocate our initial slice
        init := make([]int64, l, c)
        for j := 0; j < cap(init); j++ {
            init = append(init, 42)
        }
    }
}

输出:

$ go test peter_test.go -bench=. -benchmem
BenchmarkNoPreallocate-4       20000   75656 ns/op   287992 B/op   19 allocs/op
BenchmarkPreallocateConst-4   100000   22386 ns/op    65536 B/op    1 allocs/op
BenchmarkPreallocateVar-4     100000   22112 ns/op    65536 B/op    1 allocs/op

关于go - 解释预分配 slice 的基准,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47192729/

相关文章:

sorting - 获取int的第一个数字

go - 如何从远程机器连接到kubernetes pod?

go - Couchbase GO SDK 不区分大小写

c - C 中的字符串切片和复制

arrays - 我将如何使用散列切片来初始化存储在数据结构中的散列?

go - 构建后如何从API获取Docker镜像ID?

string - 从字符串中删除空字符

Python - 取每个第 n 个元素(就地)

javascript - String.slice 和 String.substring 有什么区别?

python - 为什么在 numpy 数组小数的切片赋值期间消失了?