bench/hash.go (144 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package benchmarks
import (
"encoding/binary"
"hash/maphash"
"unsafe"
"github.com/cespare/xxhash/v2"
hackymaphash "github.com/dolthub/maphash"
"github.com/elastic/go-freelru"
"github.com/zeebo/xxh3"
)
//go:noescape
func strhash(s string, h uintptr) uintptr
//go:noescape
func inthash(i int, h uintptr) uintptr
// PtrSize is the size of a pointer in bytes - unsafe.Sizeof(uintptr(0)) but as an ideal constant.
// It is also the size of the machine's native word size (4 on 32-bit systems, 8 on 64-bit).
const PtrSize = 4 << (^uintptr(0) >> 63)
const hashRandomBytes = PtrSize / 4 * 64
const (
// FNV-1a
offset32 = uint32(2166136261)
prime32 = uint32(16777619)
// Init32 is what 32 bits hash values should be initialized with.
Init32 = offset32
)
// used in asm_{386,amd64,arm64}.s to seed the hash function
var aeskeysched [hashRandomBytes]byte
func init() {
for i := 0; i < hashRandomBytes; i++ {
aeskeysched[i] = byte(i + 1)
}
aeskeysched[hashRandomBytes-1] = 0xFF
}
var stdMapHash maphash.Hash
func hashIntMapHash(i int) uint32 {
b := unsafe.Slice((*byte)(unsafe.Pointer(&i)), 8)
stdMapHash.Reset()
stdMapHash.Write(b)
return uint32(stdMapHash.Sum64())
}
func hashStringMapHash(s string) uint32 {
stdMapHash.WriteString(s)
return uint32(stdMapHash.Sum64())
}
var hackyIntMapHasher = hackymaphash.NewHasher[int]()
func hashIntMapHasher(i int) uint32 {
return uint32(hackyIntMapHasher.Hash(i))
}
var hackyStringMapHasher = hackymaphash.NewHasher[string]()
func hashStringMapHasher(s string) uint32 {
return uint32(hackyStringMapHasher.Hash(s))
}
func hashIntFNV1A(i int) uint32 {
b := [8]byte{}
binary.BigEndian.PutUint64(b[:], uint64(i))
h := Init32
h = (h ^ uint32(b[0])) * prime32
h = (h ^ uint32(b[1])) * prime32
h = (h ^ uint32(b[2])) * prime32
h = (h ^ uint32(b[3])) * prime32
h = (h ^ uint32(b[4])) * prime32
h = (h ^ uint32(b[5])) * prime32
h = (h ^ uint32(b[6])) * prime32
h = (h ^ uint32(b[7])) * prime32
return h
}
func hashIntFNV1AUnsafe(i int) uint32 {
b := unsafe.Slice((*byte)(unsafe.Pointer(&i)), 8)
h := Init32
h = (h ^ uint32(b[0])) * prime32
h = (h ^ uint32(b[1])) * prime32
h = (h ^ uint32(b[2])) * prime32
h = (h ^ uint32(b[3])) * prime32
h = (h ^ uint32(b[4])) * prime32
h = (h ^ uint32(b[5])) * prime32
h = (h ^ uint32(b[6])) * prime32
h = (h ^ uint32(b[7])) * prime32
return h
}
func hashIntAESENC(i int) uint32 {
return uint32(inthash(i, 0x1234bacd9876feda))
}
func getHashAESENC[K comparable]() freelru.HashKeyCallback[K] {
var k K
switch any(k).(type) {
case string:
return any((freelru.HashKeyCallback[string])(hashStringAESENC)).(freelru.HashKeyCallback[K])
case int:
return any((freelru.HashKeyCallback[int])(hashIntAESENC)).(freelru.HashKeyCallback[K])
}
return nil
}
func hashUInt32(i uint32) uint32 {
b := [4]byte{}
binary.BigEndian.PutUint32(b[:], i)
h := Init32
h = (h ^ uint32(b[0])) * prime32
h = (h ^ uint32(b[1])) * prime32
h = (h ^ uint32(b[2])) * prime32
h = (h ^ uint32(b[3])) * prime32
return h
}
// nolint: deadcode, unused
func hashBytes(b []byte) uint32 {
h := Init32
for ; len(b) >= 8; b = b[8:] {
h = (h ^ uint32(b[0])) * prime32
h = (h ^ uint32(b[1])) * prime32
h = (h ^ uint32(b[2])) * prime32
h = (h ^ uint32(b[3])) * prime32
h = (h ^ uint32(b[4])) * prime32
h = (h ^ uint32(b[5])) * prime32
h = (h ^ uint32(b[6])) * prime32
h = (h ^ uint32(b[7])) * prime32
}
if len(b) >= 4 {
h = (h ^ uint32(b[0])) * prime32
h = (h ^ uint32(b[1])) * prime32
h = (h ^ uint32(b[2])) * prime32
h = (h ^ uint32(b[3])) * prime32
b = b[4:]
}
if len(b) >= 2 {
h = (h ^ uint32(b[0])) * prime32
h = (h ^ uint32(b[1])) * prime32
b = b[2:]
}
if len(b) != 0 {
h = (h ^ uint32(b[0])) * prime32
}
return h
}
func hashStringFNV1A(s string) uint32 {
// ~2x faster than hashBytes([]byte(s)) by avoiding a copy.
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
return hashBytes(b)
}
// sliceHeader is similar to reflect.SliceHeader, but it assumes that the layout
// of the first two words is the same as the layout of a string.
type sliceHeader struct {
s string
cap int
}
func hashStringXXHASH(s string) uint32 {
return uint32(xxhash.Sum64String(s))
}
func hashStringXXH3HASH(s string) uint32 {
return uint32(xxh3.HashString(s))
}
// go:noescape
func hashStringAESENC(s string) uint32 {
return uint32(strhash(s, 0x123456789))
}
type int128 struct {
lo, hi uint64
}