Skip to content

Commit

Permalink
Add a fast path for string inputs
Browse files Browse the repository at this point in the history
```
                  │ 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%
```
  • Loading branch information
bep committed Feb 6, 2025
1 parent 72666c8 commit 25de947
Showing 1 changed file with 21 additions and 8 deletions.
29 changes: 21 additions & 8 deletions hashstructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
}
Expand Down

0 comments on commit 25de947

Please sign in to comment.