mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
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>
This commit is contained in:
parent
700badd8f8
commit
9ae3bd0939
@ -20,29 +20,67 @@
|
||||
"sync"
|
||||
)
|
||||
|
||||
func calcHash(v interface{}) string {
|
||||
h := sha256.New()
|
||||
b := bufio.NewWriterSize(h, h.BlockSize())
|
||||
scratch := make([]byte, 0, 128)
|
||||
printTo(b, v, scratch)
|
||||
b.Flush()
|
||||
scratch = h.Sum(scratch[:0])
|
||||
// The first sha256.Size bytes contain the hash.
|
||||
// Hex-encode that into the next sha256.Size*2 bytes.
|
||||
src := scratch[:sha256.Size]
|
||||
dst := scratch[sha256.Size:cap(scratch)]
|
||||
n := hex.Encode(dst, src)
|
||||
return string(dst[:n])
|
||||
// hasher is reusable state for hashing a value.
|
||||
// Get one via hasherPool.
|
||||
type hasher struct {
|
||||
h hash.Hash
|
||||
bw *bufio.Writer
|
||||
scratch [128]byte
|
||||
}
|
||||
|
||||
// UpdateHash sets last to the hash of v and reports whether its value changed.
|
||||
// newHasher initializes a new hasher, for use by hasherPool.
|
||||
func newHasher() *hasher {
|
||||
h := &hasher{h: sha256.New()}
|
||||
h.bw = bufio.NewWriterSize(h.h, h.h.BlockSize())
|
||||
return h
|
||||
}
|
||||
|
||||
// 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) {
|
||||
sig := calcHash(v)
|
||||
if *last != sig {
|
||||
*last = sig
|
||||
return true
|
||||
sum := Hash(v)
|
||||
if sha256EqualHex(sum, *last) {
|
||||
// unchanged.
|
||||
return false
|
||||
}
|
||||
return false
|
||||
*last = hex.EncodeToString(sum[:])
|
||||
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
|
||||
}
|
||||
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) {
|
||||
|
@ -7,6 +7,8 @@
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
@ -23,10 +25,10 @@ func TestDeepHash(t *testing.T) {
|
||||
// Mostly we're just testing that we don't panic on handled types.
|
||||
v := getVal()
|
||||
|
||||
hash1 := calcHash(v)
|
||||
hash1 := Hash(v)
|
||||
t.Logf("hash: %v", hash1)
|
||||
for i := 0; i < 20; i++ {
|
||||
hash2 := calcHash(getVal())
|
||||
hash2 := Hash(getVal())
|
||||
if hash1 != hash2 {
|
||||
t.Error("second hash didn't match")
|
||||
}
|
||||
@ -76,11 +78,13 @@ func getVal() []interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
var sink = Hash("foo")
|
||||
|
||||
func BenchmarkHash(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
v := getVal()
|
||||
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) {
|
||||
seen := make(map[string]bool)
|
||||
seen := make(map[[sha256.Size]byte]bool)
|
||||
for i := 0; i < 100000; i++ {
|
||||
s := calcHash(i)
|
||||
s := Hash(i)
|
||||
if seen[s] {
|
||||
t.Fatalf("hash collision %v", i)
|
||||
}
|
||||
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…
Reference in New Issue
Block a user