util/deephash: export a Hash func for use by the control plane

name              old time/op    new time/op    delta
Hash-6              69.4µs ± 6%    68.4µs ± 4%     ~     (p=0.286 n=9+9)
HashMapAcyclic-6     115µs ± 5%     115µs ± 4%     ~     (p=1.000 n=10+10)

name              old alloc/op   new alloc/op   delta
Hash-6              2.29kB ± 0%    1.88kB ± 0%  -18.13%  (p=0.000 n=10+10)
HashMapAcyclic-6    2.53kB ± 0%    2.53kB ± 0%     ~     (all equal)

name              old allocs/op  new allocs/op  delta
Hash-6                58.0 ± 0%      54.0 ± 0%   -6.90%  (p=0.000 n=10+10)
HashMapAcyclic-6       202 ± 0%       202 ± 0%     ~     (all equal)

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
simenghe/tcpnodeping
Brad Fitzpatrick 3 years ago committed by Brad Fitzpatrick
parent 700badd8f8
commit 9ae3bd0939

@ -20,30 +20,68 @@ import (
"sync" "sync"
) )
func calcHash(v interface{}) string { // hasher is reusable state for hashing a value.
h := sha256.New() // Get one via hasherPool.
b := bufio.NewWriterSize(h, h.BlockSize()) type hasher struct {
scratch := make([]byte, 0, 128) h hash.Hash
printTo(b, v, scratch) bw *bufio.Writer
b.Flush() scratch [128]byte
scratch = h.Sum(scratch[:0]) }
// The first sha256.Size bytes contain the hash.
// Hex-encode that into the next sha256.Size*2 bytes. // newHasher initializes a new hasher, for use by hasherPool.
src := scratch[:sha256.Size] func newHasher() *hasher {
dst := scratch[sha256.Size:cap(scratch)] h := &hasher{h: sha256.New()}
n := hex.Encode(dst, src) h.bw = bufio.NewWriterSize(h.h, h.h.BlockSize())
return string(dst[:n]) return h
} }
// UpdateHash sets last to the hash of v and reports whether its value changed. // Hash returns the raw SHA-256 (not hex) of v.
func (h *hasher) Hash(v interface{}) (hash [sha256.Size]byte) {
h.bw.Flush()
h.h.Reset()
printTo(h.bw, v, h.scratch[:])
h.bw.Flush()
h.h.Sum(hash[:0])
return hash
}
var hasherPool = &sync.Pool{
New: func() interface{} { return newHasher() },
}
// Hash returns the raw SHA-256 hash of v.
func Hash(v interface{}) [sha256.Size]byte {
hasher := hasherPool.Get().(*hasher)
hasherPool.Put(hasher)
return hasher.Hash(v)
}
// UpdateHash sets last to the hex-encoded hash of v and reports whether its value changed.
func UpdateHash(last *string, v ...interface{}) (changed bool) { func UpdateHash(last *string, v ...interface{}) (changed bool) {
sig := calcHash(v) sum := Hash(v)
if *last != sig { if sha256EqualHex(sum, *last) {
*last = sig // unchanged.
return false
}
*last = hex.EncodeToString(sum[:])
return true return true
} }
// sha256EqualHex reports whether hx is the hex encoding of sum.
func sha256EqualHex(sum [sha256.Size]byte, hx string) bool {
if len(hx) != len(sum)*2 {
return false return false
} }
const hextable = "0123456789abcdef"
j := 0
for _, v := range sum {
if hx[j] != hextable[v>>4] || hx[j+1] != hextable[v&0x0f] {
return false
}
j += 2
}
return true
}
func printTo(w *bufio.Writer, v interface{}, scratch []byte) { func printTo(w *bufio.Writer, v interface{}, scratch []byte) {
print(w, reflect.ValueOf(v), make(map[uintptr]bool), scratch) print(w, reflect.ValueOf(v), make(map[uintptr]bool), scratch)

@ -7,6 +7,8 @@ package deephash
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"crypto/sha256"
"encoding/hex"
"fmt" "fmt"
"reflect" "reflect"
"testing" "testing"
@ -23,10 +25,10 @@ func TestDeepHash(t *testing.T) {
// Mostly we're just testing that we don't panic on handled types. // Mostly we're just testing that we don't panic on handled types.
v := getVal() v := getVal()
hash1 := calcHash(v) hash1 := Hash(v)
t.Logf("hash: %v", hash1) t.Logf("hash: %v", hash1)
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
hash2 := calcHash(getVal()) hash2 := Hash(getVal())
if hash1 != hash2 { if hash1 != hash2 {
t.Error("second hash didn't match") t.Error("second hash didn't match")
} }
@ -76,11 +78,13 @@ func getVal() []interface{} {
} }
} }
var sink = Hash("foo")
func BenchmarkHash(b *testing.B) { func BenchmarkHash(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
v := getVal() v := getVal()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
calcHash(v) sink = Hash(v)
} }
} }
@ -136,12 +140,25 @@ func BenchmarkHashMapAcyclic(b *testing.B) {
} }
func TestExhaustive(t *testing.T) { func TestExhaustive(t *testing.T) {
seen := make(map[string]bool) seen := make(map[[sha256.Size]byte]bool)
for i := 0; i < 100000; i++ { for i := 0; i < 100000; i++ {
s := calcHash(i) s := Hash(i)
if seen[s] { if seen[s] {
t.Fatalf("hash collision %v", i) t.Fatalf("hash collision %v", i)
} }
seen[s] = true seen[s] = true
} }
} }
func TestSHA256EqualHex(t *testing.T) {
for i := 0; i < 1000; i++ {
sum := Hash(i)
hx := hex.EncodeToString(sum[:])
if !sha256EqualHex(sum, hx) {
t.Fatal("didn't match, should've")
}
if sha256EqualHex(sum, hx[:len(hx)-1]) {
t.Fatal("matched on wrong length")
}
}
}

Loading…
Cancel
Save