diff --git a/Benchmarks_test.go b/Benchmarks_test.go index 2e85bef..97b5d6a 100644 --- a/Benchmarks_test.go +++ b/Benchmarks_test.go @@ -8,10 +8,14 @@ import ( ) func BenchmarkSize(b *testing.B) { + b.Run("7", bench(7)) b.Run("8", bench(8)) b.Run("16", bench(16)) + b.Run("17", bench(17)) b.Run("32", bench(32)) + b.Run("33", bench(33)) b.Run("64", bench(64)) + b.Run("65", bench(65)) b.Run("128", bench(128)) b.Run("256", bench(256)) b.Run("512", bench(512)) diff --git a/hash.go b/hash.go index f650194..3b6a1e7 100644 --- a/hash.go +++ b/hash.go @@ -13,7 +13,7 @@ func add(x uint64, in []byte) uint64 { var i int // Cache lines on modern processors are 64 bytes long. - // A single uint64 consumes 64 bits (8 bytes). + // A single uint64 consumes 8 bytes. // That means we should read 8 uint64 at a time. for ; i < len(in)-63; i += 64 { words := (*[8]uint64)(unsafe.Pointer(&in[i])) @@ -46,5 +46,5 @@ func add(x uint64, in []byte) uint64 { } func mix(x uint64, b uint64) uint64 { - return (x + b) * 0x50003 + return (x + b) * 0xD0003 } diff --git a/hash_test.go b/hash_test.go index 0f3c813..db98e6d 100644 --- a/hash_test.go +++ b/hash_test.go @@ -7,42 +7,53 @@ import ( "git.akyoto.dev/go/hash" ) +// Hashes generated in tests will be saved to check for collisions. var hashes = map[uint64][]byte{} -func TestTinyCollisions(t *testing.T) { - for size := 1; size < 8; size++ { - tmp := make([]byte, size) - index := 0 +// addHash adds a new hash and checks for collisions. +func addHash(t *testing.T, sum uint64, data []byte) { + saved, found := hashes[sum] - for i := 0; i < 256; i++ { - tmp[index] += 1 - h := hash.Bytes(tmp) - previous, found := hashes[h] + if found && !bytes.Equal(saved, data) { + t.Fatalf("collision between %v and %v:\nhash %064b", saved, data, sum) + } - if found && !bytes.Equal(tmp, previous) { - t.Fatalf("collision between %v and %v:\nhash %064b", previous, tmp, h) - } + save := make([]byte, len(data)) + copy(save, data) + hashes[sum] = save +} - save := make([]byte, size) - copy(save, tmp) - hashes[h] = save - index = (index + 1) % size +// TestTiny hashes every single permutation that is 1-32 bytes long. +func TestTiny(t *testing.T) { + for size := 1; size <= 32; size++ { + data := make([]byte, size) + + for i := 0; i <= 255*size; i++ { + sum := hash.Bytes(data) + addHash(t, sum, data) + data[i%size] += 1 } } } -func TestZeroedCollisions(t *testing.T) { +// TestZeroed hashes completely zeroed buffers that are 1-8192 bytes long. +func TestZeroed(t *testing.T) { zero := make([]byte, 8192) for size := 1; size <= len(zero); size++ { - tmp := zero[:size] - h := hash.Bytes(tmp) - previous, found := hashes[h] - - if found && !bytes.Equal(tmp, previous) { - t.Fatalf("collision between %v and %v:\nhash %064b", previous, tmp, h) - } - - hashes[h] = tmp + data := zero[:size] + sum := hash.Bytes(data) + addHash(t, sum, data) + } +} + +// TestSameByte hashes every byte repetition that is 1-512 bytes long. +func TestSameByte(t *testing.T) { + for b := 1; b < 256; b++ { + for size := 1; size <= 512; size++ { + data := bytes.Repeat([]byte{byte(b)}, size) + sum := hash.Bytes(data) + addHash(t, sum, data) + } } }