diff --git a/internal/deephash/deephash.go b/internal/deephash/deephash.go index f7dafac4f..f3e596144 100644 --- a/internal/deephash/deephash.go +++ b/internal/deephash/deephash.go @@ -197,6 +197,7 @@ type mapHasher struct { s256 hash.Hash // sha256 hash.Hash bw *bufio.Writer // to hasher into ebuf val valueCache // re-usable values for map iteration + iter *reflect.MapIter // re-usable map iterator } func (mh *mapHasher) Reset() { @@ -226,6 +227,7 @@ var mapHasherPool = &sync.Pool{ mh.s256 = sha256.New() mh.bw = bufio.NewWriter(mh.s256) mh.val = make(valueCache) + mh.iter = new(reflect.MapIter) return mh }, } @@ -248,7 +250,8 @@ func hashMapAcyclic(w *bufio.Writer, v reflect.Value, visited map[uintptr]bool, mh := mapHasherPool.Get().(*mapHasher) defer mapHasherPool.Put(mh) mh.Reset() - iter := v.MapRange() + iter := mapIter(mh.iter, v) + defer mapIter(mh.iter, reflect.Value{}) // avoid pinning v from mh.iter when we return k := mh.val.get(v.Type().Key()) e := mh.val.get(v.Type().Elem()) for iter.Next() { diff --git a/internal/deephash/mapiter.go b/internal/deephash/mapiter.go index a47eb76b4..01a3ed1e5 100644 --- a/internal/deephash/mapiter.go +++ b/internal/deephash/mapiter.go @@ -23,3 +23,15 @@ func iterKey(iter *reflect.MapIter, _ reflect.Value) reflect.Value { func iterVal(iter *reflect.MapIter, _ reflect.Value) reflect.Value { return iter.Value() } + +// mapIter returns a map iterator for mapVal. +// scratch is a re-usable reflect.MapIter. +// mapIter may re-use scratch and return it, +// or it may allocate and return a new *reflect.MapIter. +// If mapVal is the zero reflect.Value, mapIter may return nil. +func mapIter(_ *reflect.MapIter, mapVal reflect.Value) *reflect.MapIter { + if !mapVal.IsValid() { + return nil + } + return mapVal.MapRange() +} diff --git a/internal/deephash/mapiter_future.go b/internal/deephash/mapiter_future.go index a92fbc75f..d80221d11 100644 --- a/internal/deephash/mapiter_future.go +++ b/internal/deephash/mapiter_future.go @@ -25,3 +25,18 @@ func iterVal(iter *reflect.MapIter, scratch reflect.Value) reflect.Value { iter.SetValue(scratch) return scratch } + +// mapIter returns a map iterator for mapVal. +// scratch is a re-usable reflect.MapIter. +// mapIter may re-use scratch and return it, +// or it may allocate and return a new *reflect.MapIter. +// If mapVal is the zero reflect.Value, mapIter may return nil. +func mapIter(scratch *reflect.MapIter, mapVal reflect.Value) *reflect.MapIter { + scratch.Reset(mapVal) // always Reset, to allow the caller to avoid pinning memory + if !mapVal.IsValid() { + // Returning scratch would also be OK. + // Do this for consistency with the non-optimized version. + return nil + } + return scratch +}