对于大量关键点,go maps无性能
最近我发现围棋地图的行为非常奇怪。用例是创建一组整数,并对IsMember(id int)进行O(1)检查 目前的实施是:对于大量关键点,go maps无性能,go,hashmap,maps,hash-collision,Go,Hashmap,Maps,Hash Collision,最近我发现围棋地图的行为非常奇怪。用例是创建一组整数,并对IsMember(id int)进行O(1)检查 目前的实施是: func convertToMap(v []int64) map[int64]void { out := make(map[int64]void, len(v)) for _, i := range v { out[i] = void{} } return out } type Group struct { membe
func convertToMap(v []int64) map[int64]void {
out := make(map[int64]void, len(v))
for _, i := range v {
out[i] = void{}
}
return out
}
type Group struct {
members map[int64]void
}
type void struct{}
func (g *Group) IsMember(input string) (ok bool) {
memberID, _ := strconv.ParseInt(input, 10, 64)
_, ok = g.members[memberID]
return
}
当我对IsMember方法进行基准测试时,直到有600万成员,一切看起来都很好。但除此之外,每次查找地图都需要1秒的时间
基准测试:
func BenchmarkIsMember(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
g := &Group{}
g.members = convertToMap(benchmarkV)
for N := 0; N < b.N && N < sizeOfGroup; N++ {
g.IsMember(benchmarkKVString[N])
}
}
var benchmarkV, benchmarkKVString = func(size int) ([]int64, []string{
v := make([]int64, size)
s := make([]string, size)
for i := range v {
val := rand.Int63()
v[i] = val
s[i] = strconv.FormatInt(val, 10)
}
return v, s
}(sizeOfGroup)
超过680万的团体规模也会产生同样的结果
有人能帮我解释一下为什么会发生这种情况吗?在仍然使用地图的情况下,能做些什么来让它发挥作用吗
而且,我不明白为什么要分配这么多内存?即使所花费的时间是由于冲突和链表遍历,也不应该有任何mem分配,我的思维过程是否错误?无需测量将切片转换为映射所需的额外分配,因为我们只想测量查找操作 我稍微修改了基准:
func BenchmarkIsMember(b *testing.B) {
fn := func(size int) ([]int64, []string) {
v := make([]int64, size)
s := make([]string, size)
for i := range v {
val := rand.Int63()
v[i] = val
s[i] = strconv.FormatInt(val, 10)
}
return v, s
}
for _, size := range []int{
6000000,
6800000,
6830000,
60000000,
} {
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
var benchmarkV, benchmarkKVString = fn(size)
g := &deltaGroup{}
g.members = convertToMap(benchmarkV)
b.ReportAllocs()
b.ResetTimer()
for N := 0; N < b.N && N < size; N++ {
g.IsMember(benchmarkKVString[N])
}
})
}
}
降级并不像您的示例中那样严重。您的代码无法编译。
我不明白为什么要分配这么多内存。
。这是一个微妙的说法,因为您已经编写了make(map[int64]void,len(v))
。另见@mh cbon如果是这样,那么600万的基准也应该显示相同的mem分配。600万和680万的基准在查找时间和内存分配方面都有很大的差异well@mh-在cbon nevermind上,基准也在计算将切片转换为映射的分配。了解问题600万和680万的基准在查找时间和内存分配方面都有很大的差异
。一些运行时行为,但不确定。请参阅pool.Buffer。
func BenchmarkIsMember(b *testing.B) {
fn := func(size int) ([]int64, []string) {
v := make([]int64, size)
s := make([]string, size)
for i := range v {
val := rand.Int63()
v[i] = val
s[i] = strconv.FormatInt(val, 10)
}
return v, s
}
for _, size := range []int{
6000000,
6800000,
6830000,
60000000,
} {
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
var benchmarkV, benchmarkKVString = fn(size)
g := &deltaGroup{}
g.members = convertToMap(benchmarkV)
b.ReportAllocs()
b.ResetTimer()
for N := 0; N < b.N && N < size; N++ {
g.IsMember(benchmarkKVString[N])
}
})
}
}
go test ./... -bench=. -benchtime=10s -cpu=1
goos: linux
goarch: amd64
pkg: trash
BenchmarkIsMember/size=6000000 2000000000 0.55 ns/op 0 B/op 0 allocs/op
BenchmarkIsMember/size=6800000 1000000000 1.27 ns/op 0 B/op 0 allocs/op
BenchmarkIsMember/size=6830000 1000000000 1.23 ns/op 0 B/op 0 allocs/op
BenchmarkIsMember/size=60000000 100000000 136 ns/op 0 B/op 0 allocs/op
PASS
ok trash 167.578s