From 25de947cc917dedafbb25b1954679617db675e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Thu, 6 Feb 2025 10:29:07 +0100 Subject: [PATCH] Add a fast path for string inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` │ master.bench │ perf-string.bench │ │ sec/op │ sec/op vs base │ String/default-10 86.98n ± 3% 77.57n ± 1% -10.82% (p=0.002 n=6) String/xxhash-10 40.10n ± 0% 34.82n ± 4% -13.19% (p=0.002 n=6) geomean 59.06n 51.97n -12.01% │ master.bench │ perf-string.bench │ │ B/op │ B/op vs base │ String/default-10 56.00 ± 0% 56.00 ± 0% ~ (p=1.000 n=6) ¹ String/xxhash-10 16.00 ± 0% 16.00 ± 0% ~ (p=1.000 n=6) ¹ geomean 29.93 29.93 +0.00% ¹ all samples are equal │ master.bench │ perf-string.bench │ │ allocs/op │ allocs/op vs base │ String/default-10 3.000 ± 0% 3.000 ± 0% ~ (p=1.000 n=6) ¹ String/xxhash-10 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=6) ¹ geomean 1.732 1.732 +0.00% ``` --- hashstructure.go | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/hashstructure.go b/hashstructure.go index fe5ffd2..ad1081f 100644 --- a/hashstructure.go +++ b/hashstructure.go @@ -91,6 +91,11 @@ func Hash(v interface{}, opts *HashOptions) (uint64, error) { // Reset the hash opts.Hasher.Reset() + // Fast path for strings. + if s, ok := v.(string); ok { + return hashString(opts.Hasher, s) + } + // Create our walker and walk the structure w := &walker{ h: opts.Hasher, @@ -130,6 +135,21 @@ func (w *walker) hashDirect(v any) (uint64, error) { return w.h.Sum64(), err } +// A direct hash calculation used for strings. +func (w *walker) hashString(s string) (uint64, error) { + return hashString(w.h, s) +} + +// A direct hash calculation used for strings. +func hashString(h hash.Hash64, s string) (uint64, error) { + h.Reset() + + // io.WriteString uses io.StringWriter if it exists, which is + // implemented by e.g. github.com/cespare/xxhash. + _, err := io.WriteString(h, s) + return h.Sum64(), err +} + func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) { t := reflect.TypeOf(0) @@ -405,14 +425,7 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) { return h, nil case reflect.String: - // Directly hash - w.h.Reset() - - // io.WriteString uses io.StringWriter if it exists, which is - // implemented by e.g. github.com/cespare/xxhash. - _, err := io.WriteString(w.h, v.String()) - return w.h.Sum64(), err - + return w.hashString(v.String()) default: return 0, fmt.Errorf("unknown kind to hash: %s", k) }