util/deephash: move funcs to methods

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2021-07-06 22:37:32 -07:00
parent 9288e0d61c
commit aceaa70b16
2 changed files with 61 additions and 36 deletions

View File

@ -27,20 +27,34 @@ type hasher struct {
h hash.Hash h hash.Hash
bw *bufio.Writer bw *bufio.Writer
scratch [128]byte scratch [128]byte
visited map[uintptr]bool
} }
// newHasher initializes a new hasher, for use by hasherPool. // newHasher initializes a new hasher, for use by hasherPool.
func newHasher() *hasher { func newHasher() *hasher {
h := &hasher{h: sha256.New()} h := &hasher{
h: sha256.New(),
visited: map[uintptr]bool{},
}
h.bw = bufio.NewWriterSize(h.h, h.h.BlockSize()) h.bw = bufio.NewWriterSize(h.h, h.h.BlockSize())
return h return h
} }
// setBufioWriter switches the bufio writer to w after flushing
// any output to the old one. It then also returns the old one, so
// the caller can switch back to it.
func (h *hasher) setBufioWriter(w *bufio.Writer) (old *bufio.Writer) {
old = h.bw
old.Flush()
h.bw = w
return old
}
// Hash returns the raw SHA-256 (not hex) of v. // Hash returns the raw SHA-256 (not hex) of v.
func (h *hasher) Hash(v interface{}) (hash [sha256.Size]byte) { func (h *hasher) Hash(v interface{}) (hash [sha256.Size]byte) {
h.bw.Flush() h.bw.Flush()
h.h.Reset() h.h.Reset()
printTo(h.bw, v, h.scratch[:]) h.print(reflect.ValueOf(v))
h.bw.Flush() h.bw.Flush()
h.h.Sum(hash[:0]) h.h.Sum(hash[:0])
return hash return hash
@ -52,9 +66,12 @@ func (h *hasher) Hash(v interface{}) (hash [sha256.Size]byte) {
// Hash returns the raw SHA-256 hash of v. // Hash returns the raw SHA-256 hash of v.
func Hash(v interface{}) [sha256.Size]byte { func Hash(v interface{}) [sha256.Size]byte {
hasher := hasherPool.Get().(*hasher) h := hasherPool.Get().(*hasher)
defer hasherPool.Put(hasher) defer hasherPool.Put(h)
return hasher.Hash(v) for k := range h.visited {
delete(h.visited, k)
}
return h.Hash(v)
} }
// UpdateHash sets last to the hex-encoded hash of v and reports whether its value changed. // UpdateHash sets last to the hex-encoded hash of v and reports whether its value changed.
@ -84,10 +101,6 @@ func sha256EqualHex(sum [sha256.Size]byte, hx string) bool {
return true return true
} }
func printTo(w *bufio.Writer, v interface{}, scratch []byte) {
print(w, reflect.ValueOf(v), make(map[uintptr]bool), scratch)
}
var appenderToType = reflect.TypeOf((*appenderTo)(nil)).Elem() var appenderToType = reflect.TypeOf((*appenderTo)(nil)).Elem()
type appenderTo interface { type appenderTo interface {
@ -96,16 +109,19 @@ type appenderTo interface {
// print hashes v into w. // print hashes v into w.
// It reports whether it was able to do so without hitting a cycle. // It reports whether it was able to do so without hitting a cycle.
func print(w *bufio.Writer, v reflect.Value, visited map[uintptr]bool, scratch []byte) (acyclic bool) { func (h *hasher) print(v reflect.Value) (acyclic bool) {
if !v.IsValid() { if !v.IsValid() {
return true return true
} }
w := h.bw
visited := h.visited
if v.CanInterface() { if v.CanInterface() {
// Use AppendTo methods, if available and cheap. // Use AppendTo methods, if available and cheap.
if v.CanAddr() && v.Type().Implements(appenderToType) { if v.CanAddr() && v.Type().Implements(appenderToType) {
a := v.Addr().Interface().(appenderTo) a := v.Addr().Interface().(appenderTo)
scratch = a.AppendTo(scratch[:0]) scratch := a.AppendTo(h.scratch[:0])
w.Write(scratch) w.Write(scratch)
return true return true
} }
@ -121,13 +137,13 @@ func print(w *bufio.Writer, v reflect.Value, visited map[uintptr]bool, scratch [
return false return false
} }
visited[ptr] = true visited[ptr] = true
return print(w, v.Elem(), visited, scratch) return h.print(v.Elem())
case reflect.Struct: case reflect.Struct:
acyclic = true acyclic = true
w.WriteString("struct{\n") w.WriteString("struct{\n")
for i, n := 0, v.NumField(); i < n; i++ { for i, n := 0, v.NumField(); i < n; i++ {
fmt.Fprintf(w, " [%d]: ", i) fmt.Fprintf(w, " [%d]: ", i)
if !print(w, v.Field(i), visited, scratch) { if !h.print(v.Field(i)) {
acyclic = false acyclic = false
} }
w.WriteString("\n") w.WriteString("\n")
@ -143,7 +159,7 @@ func print(w *bufio.Writer, v reflect.Value, visited map[uintptr]bool, scratch [
acyclic = true acyclic = true
for i, ln := 0, v.Len(); i < ln; i++ { for i, ln := 0, v.Len(); i < ln; i++ {
fmt.Fprintf(w, " [%d]: ", i) fmt.Fprintf(w, " [%d]: ", i)
if !print(w, v.Index(i), visited, scratch) { if !h.print(v.Index(i)) {
acyclic = false acyclic = false
} }
w.WriteString("\n") w.WriteString("\n")
@ -151,24 +167,22 @@ func print(w *bufio.Writer, v reflect.Value, visited map[uintptr]bool, scratch [
w.WriteString("}\n") w.WriteString("}\n")
return acyclic return acyclic
case reflect.Interface: case reflect.Interface:
return print(w, v.Elem(), visited, scratch) return h.print(v.Elem())
case reflect.Map: case reflect.Map:
if hashMapAcyclic(w, v, visited, scratch) { if h.hashMapAcyclic(v) {
return true return true
} }
return hashMapFallback(w, v, visited, scratch) return h.hashMapFallback(v)
case reflect.String: case reflect.String:
w.WriteString(v.String()) w.WriteString(v.String())
case reflect.Bool: case reflect.Bool:
w.Write(strconv.AppendBool(scratch[:0], v.Bool())) w.Write(strconv.AppendBool(h.scratch[:0], v.Bool()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
w.Write(strconv.AppendInt(scratch[:0], v.Int(), 10)) w.Write(strconv.AppendInt(h.scratch[:0], v.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
scratch = strconv.AppendUint(scratch[:0], v.Uint(), 10) w.Write(strconv.AppendUint(h.scratch[:0], v.Uint(), 10))
w.Write(scratch)
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
scratch = strconv.AppendUint(scratch[:0], math.Float64bits(v.Float()), 10) w.Write(strconv.AppendUint(h.scratch[:0], math.Float64bits(v.Float()), 10))
w.Write(scratch)
case reflect.Complex64, reflect.Complex128: case reflect.Complex64, reflect.Complex128:
fmt.Fprintf(w, "%v", v.Complex()) fmt.Fprintf(w, "%v", v.Complex())
} }
@ -230,40 +244,46 @@ func (c valueCache) get(t reflect.Type) reflect.Value {
// hashMapAcyclic is the faster sort-free version of map hashing. If // hashMapAcyclic is the faster sort-free version of map hashing. If
// it detects a cycle it returns false and guarantees that nothing was // it detects a cycle it returns false and guarantees that nothing was
// written to w. // written to w.
func hashMapAcyclic(w *bufio.Writer, v reflect.Value, visited map[uintptr]bool, scratch []byte) (acyclic bool) { func (h *hasher) hashMapAcyclic(v reflect.Value) (acyclic bool) {
mh := mapHasherPool.Get().(*mapHasher) mh := mapHasherPool.Get().(*mapHasher)
defer mapHasherPool.Put(mh) defer mapHasherPool.Put(mh)
mh.Reset() mh.Reset()
iter := mapIter(mh.iter, v) iter := mapIter(mh.iter, v)
defer mapIter(mh.iter, reflect.Value{}) // avoid pinning v from mh.iter when we return defer mapIter(mh.iter, reflect.Value{}) // avoid pinning v from mh.iter when we return
// Temporarily switch to the map hasher's bufio.Writer.
oldw := h.setBufioWriter(mh.bw)
defer h.setBufioWriter(oldw)
k := mh.val.get(v.Type().Key()) k := mh.val.get(v.Type().Key())
e := mh.val.get(v.Type().Elem()) e := mh.val.get(v.Type().Elem())
for iter.Next() { for iter.Next() {
key := iterKey(iter, k) key := iterKey(iter, k)
val := iterVal(iter, e) val := iterVal(iter, e)
mh.startEntry() mh.startEntry()
if !print(mh.bw, key, visited, scratch) { if !h.print(key) {
return false return false
} }
if !print(mh.bw, val, visited, scratch) { if !h.print(val) {
return false return false
} }
mh.endEntry() mh.endEntry()
} }
w.Write(mh.xbuf[:]) oldw.Write(mh.xbuf[:])
return true return true
} }
func hashMapFallback(w *bufio.Writer, v reflect.Value, visited map[uintptr]bool, scratch []byte) (acyclic bool) { func (h *hasher) hashMapFallback(v reflect.Value) (acyclic bool) {
acyclic = true acyclic = true
sm := newSortedMap(v) sm := newSortedMap(v)
w := h.bw
fmt.Fprintf(w, "map[%d]{\n", len(sm.Key)) fmt.Fprintf(w, "map[%d]{\n", len(sm.Key))
for i, k := range sm.Key { for i, k := range sm.Key {
if !print(w, k, visited, scratch) { if !h.print(k) {
acyclic = false acyclic = false
} }
w.WriteString(": ") w.WriteString(": ")
if !print(w, sm.Value[i], visited, scratch) { if !h.print(sm.Value[i]) {
acyclic = false acyclic = false
} }
w.WriteString("\n") w.WriteString("\n")

View File

@ -149,12 +149,14 @@ func TestHashMapAcyclic(t *testing.T) {
bw := bufio.NewWriter(&buf) bw := bufio.NewWriter(&buf)
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
visited := map[uintptr]bool{}
scratch := make([]byte, 0, 64)
v := reflect.ValueOf(m) v := reflect.ValueOf(m)
buf.Reset() buf.Reset()
bw.Reset(&buf) bw.Reset(&buf)
if !hashMapAcyclic(bw, v, visited, scratch) { h := &hasher{
bw: bw,
visited: map[uintptr]bool{},
}
if !h.hashMapAcyclic(v) {
t.Fatal("returned false") t.Fatal("returned false")
} }
if got[string(buf.Bytes())] { if got[string(buf.Bytes())] {
@ -176,14 +178,17 @@ func BenchmarkHashMapAcyclic(b *testing.B) {
var buf bytes.Buffer var buf bytes.Buffer
bw := bufio.NewWriter(&buf) bw := bufio.NewWriter(&buf)
visited := map[uintptr]bool{}
scratch := make([]byte, 0, 64)
v := reflect.ValueOf(m) v := reflect.ValueOf(m)
h := &hasher{
bw: bw,
visited: map[uintptr]bool{},
}
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
buf.Reset() buf.Reset()
bw.Reset(&buf) bw.Reset(&buf)
if !hashMapAcyclic(bw, v, visited, scratch) { if !h.hashMapAcyclic(v) {
b.Fatal("returned false") b.Fatal("returned false")
} }
} }