Skip to content

Commit

Permalink
optimize hashing and fix hashing support for ARM cpus (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
cornelk authored Aug 28, 2022
1 parent cda8984 commit 152772b
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 37 deletions.
23 changes: 16 additions & 7 deletions hashmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,19 +215,28 @@ func (m *HashMap[Key, Value]) setDefaultHasher() {
var key Key
switch any(key).(type) {
case string:
m.hasher = m.stringHasher
m.hasher = m.xxHashString
case int, uint, uintptr:
m.hasher = m.uintptrHasher
switch intSizeBytes {
case 2:
m.hasher = m.xxHashWord
case 4:
m.hasher = m.xxHashDword
case 8:
m.hasher = m.xxHashQword
default:
panic(fmt.Errorf("unsupported integer byte size %d", intSizeBytes))
}
case int8, uint8:
m.hasher = m.byteHasher
m.hasher = m.xxHashByte
case int16, uint16:
m.hasher = m.wordHasher
m.hasher = m.xxHashWord
case int32, uint32, float32:
m.hasher = m.dwordHasher
m.hasher = m.xxHashDword
case int64, uint64, float64, complex64:
m.hasher = m.qwordHasher
m.hasher = m.xxHashQword
case complex128:
m.hasher = m.owordHasher
m.hasher = m.xxHashOword
default:
panic(fmt.Errorf("unsupported key type %T", key))
}
Expand Down
176 changes: 146 additions & 30 deletions util_hash.go
Original file line number Diff line number Diff line change
@@ -1,72 +1,188 @@
package hashmap

/*
Copyright (c) 2016 Caleb Spare
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import (
"math/bits"
"reflect"
"unsafe"

"github.com/cespare/xxhash"
)

func (m *HashMap[Key, Value]) stringHasher(key Key) uintptr {
sh := (*reflect.StringHeader)(unsafe.Pointer(&key))
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
}
buf := *(*[]byte)(unsafe.Pointer(&bh))
return uintptr(xxhash.Sum64(buf))
}
const (
prime1 uint64 = 11400714785074694791
prime2 uint64 = 14029467366897019727
prime3 uint64 = 1609587929392839161
prime4 uint64 = 9650029242287828579
prime5 uint64 = 2870177450012600261
)

func (m *HashMap[Key, Value]) uintptrHasher(key Key) uintptr {
bh := reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(&key)),
Len: intSizeBytes,
}
buf := *(*[]byte)(unsafe.Pointer(&bh))
return uintptr(xxhash.Sum64(buf))
}
// Specialized xxhash hash functions, optimized for the bit size of the key where available,
// for all supported types beside string.

func (m *HashMap[Key, Value]) byteHasher(key Key) uintptr {
func (m *HashMap[Key, Value]) xxHashByte(key Key) uintptr {
bh := reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(&key)),
Len: 1,
}
buf := *(*[]byte)(unsafe.Pointer(&bh))
return uintptr(xxhash.Sum64(buf))
b := *(*[]byte)(unsafe.Pointer(&bh))

var h = prime5 + 1
h ^= uint64(b[0]) * prime5
h = bits.RotateLeft64(h, 11) * prime1

h ^= h >> 33
h *= prime2
h ^= h >> 29
h *= prime3
h ^= h >> 32

return uintptr(h)
}

func (m *HashMap[Key, Value]) wordHasher(key Key) uintptr {
func (m *HashMap[Key, Value]) xxHashWord(key Key) uintptr {
bh := reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(&key)),
Len: 2,
}
buf := *(*[]byte)(unsafe.Pointer(&bh))
return uintptr(xxhash.Sum64(buf))
b := *(*[]byte)(unsafe.Pointer(&bh))

var h = prime5 + 2

h ^= uint64(b[0]) * prime5
h = bits.RotateLeft64(h, 11) * prime1
h ^= uint64(b[1]) * prime5
h = bits.RotateLeft64(h, 11) * prime1

h ^= h >> 33
h *= prime2
h ^= h >> 29
h *= prime3
h ^= h >> 32

return uintptr(h)
}

func (m *HashMap[Key, Value]) dwordHasher(key Key) uintptr {
func (m *HashMap[Key, Value]) xxHashDword(key Key) uintptr {
bh := reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(&key)),
Len: 4,
}
buf := *(*[]byte)(unsafe.Pointer(&bh))
return uintptr(xxhash.Sum64(buf))
b := *(*[]byte)(unsafe.Pointer(&bh))

var h = prime5 + 4
h ^= (uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24) * prime1
h = bits.RotateLeft64(h, 23)*prime2 + prime3

h ^= h >> 33
h *= prime2
h ^= h >> 29
h *= prime3
h ^= h >> 32

return uintptr(h)
}

func (m *HashMap[Key, Value]) qwordHasher(key Key) uintptr {
func (m *HashMap[Key, Value]) xxHashQword(key Key) uintptr {
bh := reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(&key)),
Len: 8,
}
buf := *(*[]byte)(unsafe.Pointer(&bh))
return uintptr(xxhash.Sum64(buf))
b := *(*[]byte)(unsafe.Pointer(&bh))

var h = prime5 + 8

val := uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56

// inline round()
k1 := val * prime2
k1 = bits.RotateLeft64(k1, 31)
k1 *= prime1

h ^= k1
h = bits.RotateLeft64(h, 27)*prime1 + prime4

h ^= h >> 33
h *= prime2
h ^= h >> 29
h *= prime3
h ^= h >> 32

return uintptr(h)
}

func (m *HashMap[Key, Value]) owordHasher(key Key) uintptr {
func (m *HashMap[Key, Value]) xxHashOword(key Key) uintptr {
bh := reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(&key)),
Len: 16,
}
b := *(*[]byte)(unsafe.Pointer(&bh))

var h = prime5 + 16

val := uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56

// inline round()
k1 := val * prime2
k1 = bits.RotateLeft64(k1, 31)
k1 *= prime1

h ^= k1
h = bits.RotateLeft64(h, 27)*prime1 + prime4

val = uint64(b[8]) | uint64(b[9])<<8 | uint64(b[10])<<16 | uint64(b[11])<<24 |
uint64(b[12])<<32 | uint64(b[13])<<40 | uint64(b[14])<<48 | uint64(b[15])<<56

// inline round()
k1 = val * prime2
k1 = bits.RotateLeft64(k1, 31)
k1 *= prime1

h ^= k1
h = bits.RotateLeft64(h, 27)*prime1 + prime4

h ^= h >> 33
h *= prime2
h ^= h >> 29
h *= prime3
h ^= h >> 32

return uintptr(h)
}

func (m *HashMap[Key, Value]) xxHashString(key Key) uintptr {
sh := (*reflect.StringHeader)(unsafe.Pointer(&key))
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len, // cap needs to be set, otherwise xxhash fails on ARM Macs
}
buf := *(*[]byte)(unsafe.Pointer(&bh))
return uintptr(xxhash.Sum64(buf))
}

0 comments on commit 152772b

Please sign in to comment.