所以我试图制作一个超轻,故意内存很重,但非常快速的哈希表,非常快速的查找,我不关心内存使用情况,我不在乎它是否是一个罕见的错误。
基本上它只是创建一个巨大的数组(是数组,而不是切片),使用修改后的FNVa哈希(修改为仅在数组边界内提供哈希)散列字符串,然后使用哈希保存或查找值数组索引。从理论上讲,这应该是存储和检索key =>值对的最快方法。
这是我的基准:
package main
import (
"fmt"
"time"
)
const dicsize250 = 2097152000 // tested 115 collisions
type Dictionary250_uint16 struct {
dictionary [dicsize250]uint16
}
func (d *Dictionary250_uint16) Add(s string, v uint16) {
i := id(s,dicsize250)
d.dictionary[i]=v
return
}
func (d *Dictionary250_uint16) Delete(s string) {
i := id(s,dicsize250)
d.dictionary[i]=0
return
}
func (d *Dictionary250_uint16) Exists(s string) bool {
i := id(s,dicsize250)
if d.dictionary[i]==0 {
return false
} else {
return true
}
}
func (d *Dictionary250_uint16) Find(s string) uint16 {
i := id(s,dicsize250)
return d.dictionary[i]
}
// This is a FNVa hash algorithm, modified to limit to dicsize
func id(s string, dicsize uint64) uint64 {
var hash uint64 = 2166136261
for _, c := range s {
hash = (hash^uint64(c))*16777619
}
return hash%dicsize
}
var donothing bool
func main() {
dic := new(Dictionary250_uint16)
dic.Add(`test1`,10)
dic.Add(`test2`,20)
dic.Add(`test3`,30)
dic.Add(`test4`,40)
dic.Add(`test5`,50)
mc := make(map[string]uint16)
mc[`test1`]=10
mc[`test2`]=10
mc[`test3`]=10
mc[`test4`]=10
mc[`test5`]=10
var t1 uint
var t2 uint
var t3 uint
donothing = true
// Dic hit
t1 = uint(time.Now().UnixNano())
for i:=0; i<50000000; i++ {
if dic.Exists(`test4`) {
donothing = true
}
}
t3 = uint(time.Now().UnixNano())
t2 = t3-t1
fmt.Println("Dic (hit) took ",t2)
// Dic miss
t1 = uint(time.Now().UnixNano())
for i:=0; i<50000000; i++ {
if dic.Exists(`whate`) {
donothing = true
}
}
t3 = uint(time.Now().UnixNano())
t2 = t3-t1
fmt.Println("Dic (miss) took ",t2)
// Map hit
t1 = uint(time.Now().UnixNano())
for i:=0; i<50000000; i++ {
_,ok := mc[`test4`]
if ok {
donothing=true
}
}
t3 = uint(time.Now().UnixNano())
t2 = t3-t1
fmt.Println("Map (hit) took ",t2)
// Map miss
t1 = uint(time.Now().UnixNano())
for i:=0; i<50000000; i++ {
_,ok := mc[`whate`]
if ok {
donothing=true
}
}
t3 = uint(time.Now().UnixNano())
t2 = t3-t1
fmt.Println("Map (miss) took ",t2)
donothing = false
}
我得到的结果是:
Dic (hit) took 2,858,604,059
Dic (miss) took 2,457,173,526
Map (hit) took 1,574,306,146
Map (miss) took 2,525,206,080
基本上我的哈希表实现比使用地图慢得多,特别是在命中时。我不明白这是怎么可能的,因为map
是一个繁重的实现(相比之下),它没有任何碰撞,并且做了更多的计算。虽然我的实现非常简单,并且依赖于拥有大量所有可能的索引。
我做错了什么?
答案 0 :(得分:3)
首先,与内置地图相比,你使用了大量的内存,但这是你提到的想要做的交易。
使用标准库基准实用程序。它将为您提供一个坚实的工作基础,更轻松的分析访问,并消除大量的猜测。我有时间将您的一些代码剪切并粘贴到基准测试中:
func BenchmarkDictHit(b *testing.B) {
donothing = true
dic := new(Dictionary250_uint16)
dic.Add(`test1`, 10)
dic.Add(`test2`, 20)
dic.Add(`test3`, 30)
dic.Add(`test4`, 40)
dic.Add(`test5`, 50)
// The initial Dict allocation is very expensive!
b.ResetTimer()
for i := 0; i < b.N; i++ {
if dic.Exists(`test4`) {
donothing = true
}
}
}
func BenchmarkDictMiss(b *testing.B) {
donothing = true
dic := new(Dictionary250_uint16)
dic.Add(`test1`, 10)
dic.Add(`test2`, 20)
dic.Add(`test3`, 30)
dic.Add(`test4`, 40)
dic.Add(`test5`, 50)
// The initial Dict allocation is very expensive!
b.ResetTimer()
for i := 0; i < b.N; i++ {
if dic.Exists(`test6`) {
donothing = true
}
}
}
func BenchmarkMapHit(b *testing.B) {
donothing = true
mc := make(map[string]uint16)
mc[`test1`] = 10
mc[`test2`] = 10
mc[`test3`] = 10
mc[`test4`] = 10
mc[`test5`] = 10
b.ResetTimer()
// Map hit
for i := 0; i < b.N; i++ {
_, ok := mc[`test4`]
if ok {
donothing = true
}
}
donothing = false
}
func BenchmarkMapMiss(b *testing.B) {
donothing = true
mc := make(map[string]uint16)
mc[`test1`] = 10
mc[`test2`] = 10
mc[`test3`] = 10
mc[`test4`] = 10
mc[`test5`] = 10
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, ok := mc[`test6`]
if ok {
donothing = true
}
}
donothing = false
}
如果没有ResetTimer()
调用,您的支持切片的初始分配将占据基准时间,即使在整个运行过程中摊销,也会严重影响结果。重置后,基准时间按顺序显示:
BenchmarkDictHit 50000000 39.6 ns/op 0 B/op 0 allocs/op
BenchmarkDictMiss 50000000 39.1 ns/op 0 B/op 0 allocs/op
BenchmarkMapHit 100000000 22.9 ns/op 0 B/op 0 allocs/op
BenchmarkMapMiss 50000000 36.8 ns/op 0 B/op 0 allocs/op
您的id
函数需要迭代字符串。对于字符串,范围不会迭代字节,它会查找更昂贵的符文。您将希望直接索引字符串,或者可能始终使用[]byte
(大致相同的成本)。通过更好的字符串处理,这些是我测试的最终时间。
BenchmarkDictHit 100000000 17.8 ns/op 0 B/op 0 allocs/op
BenchmarkDictMiss 100000000 17.2 ns/op 0 B/op 0 allocs/op
答案 1 :(得分:1)
以下是我的JimB基准版原始版本的结果:
BenchmarkDictHit 30000000 40.8 ns/op 0 B/op 0 allocs/op
BenchmarkDictMiss 30000000 40.6 ns/op 0 B/op 0 allocs/op
BenchmarkMapHit 100000000 20.3 ns/op 0 B/op 0 allocs/op
BenchmarkMapMiss 50000000 29.5 ns/op 0 B/op 0 allocs/op
在有利的情况下,Go的地图实施速度非常快。总的来说,你的基准是人为的,毫无意义。