首页 > 解决方案 > Go sync.pool 比 make 慢很多吗?

问题描述

我尝试使用sync.Pool来重用[]byte. 但事实证明它比 make 慢。代码:

package main

import (
    "sync"
    "testing"
)

func BenchmarkMakeStack(b *testing.B) {
    for N := 0; N < b.N; N++ {
        obj := make([]byte, 1024)
        _ = obj
    }
}

var bytePool = sync.Pool{
    New: func() interface{} {
        b := make([]byte, 1024)
        return &b
    },
}

func BenchmarkBytePool(b *testing.B) {
    for N := 0; N < b.N; N++ {
        obj := bytePool.Get().(*[]byte)
        _ = obj
        bytePool.Put(obj)
    }
}

结果:

$ go test pool_test.go -bench=. -benchmem
BenchmarkMakeStack-4    2000000000      0.29 ns/op       0 B/op    0 allocs/op
BenchmarkBytePool-4      100000000     17.2 ns/op        0 B/op    0 allocs/op

根据 Go 文档,sync.Pool应该更快,但我的测试显示并非如此。有人可以帮我解释一下吗?

更新: 1. 使用 go benchmark 更新有问题的代码。2.答案放在堆栈堆中,请参阅peterSO的答案。

标签: go

解决方案


基准第一定律:无意义的微基准会产生无意义的结果。

你不切实际的微基准没有意义。


包同步

import "sync"

类型池

池是一组可以单独保存和检索的临时对象。

存储在池中的任何项目都可能随时自动删除,恕不另行通知。如果在发生这种情况时 Pool 拥有唯一的引用,则该项目可能会被释放。

一个 Pool 可以安全地同时被多个 goroutine 使用。

Pool 的目的是缓存已分配但未使用的项目以供以后重用,减轻垃圾收集器的压力。也就是说,它使构建高效、线程安全的空闲列表变得容易。但是,它并不适用于所有空闲列表。

Pool 的适当用途是管理一组在包的并发独立客户端之间静默共享并可能被重用的临时项目。Pool 提供了一种在许多客户端之间分摊分配开销的方法。

一个很好地使用池的例子是在 fmt 包中,它维护一个动态大小的临时输出缓冲区存储。存储在负载下扩展(当许多 goroutine 正在积极打印时)并在静止时缩小。

另一方面,作为短期对象的一部分维护的空闲列表不适合用于池,因为在这种情况下开销不能很好地摊销。让这些对象实现它们自己的空闲列表更有效。

sync.Pool适合您的用例吗?sync.Pool适合您的基准吗?您的用例和基准测试是否相同?您的用例是微基准测试吗?


使用 Gotesting包进行人工基准测试,对make堆栈和堆分配进行单独的基准测试,makesync.Pool.

输出:

$ go test pool_test.go -bench=. -benchmem
BenchmarkMakeStack-4    2000000000      0.29 ns/op       0 B/op    0 allocs/op
BenchmarkMakeHeap-4       10000000    136 ns/op       1024 B/op    1 allocs/op
BenchmarkBytePool-4      100000000     17.2 ns/op        0 B/op    0 allocs/op
$

pool_test.go

package main

import (
    "sync"
    "testing"
)

func BenchmarkMakeStack(b *testing.B) {
    for N := 0; N < b.N; N++ {
        obj := make([]byte, 1024)
        _ = obj
    }
}

var obj []byte

func BenchmarkMakeHeap(b *testing.B) {
    for N := 0; N < b.N; N++ {
        obj = make([]byte, 1024)
        _ = obj
    }
}

var bytePool = sync.Pool{
    New: func() interface{} {
        b := make([]byte, 1024)
        return &b
    },
}

func BenchmarkBytePool(b *testing.B) {
    for N := 0; N < b.N; N++ {
        obj := bytePool.Get().(*[]byte)
        _ = obj
        bytePool.Put(obj)
    }
}

推荐阅读