@@ -5,9 +5,10 @@ import (
55 "fmt"
66 "hash"
77 "hash/fnv"
8- "io "
8+ "math "
99 "reflect"
1010 "time"
11+ "unsafe"
1112)
1213
1314// HashOptions are options that are available for hashing.
@@ -115,6 +116,7 @@ type walker struct {
115116 ignorezerovalue bool
116117 sets bool
117118 stringer bool
119+ buf [16 ]byte // Reusable buffer for binary encoding
118120}
119121
120122type visitOpts struct {
@@ -131,8 +133,54 @@ var timeType = reflect.TypeOf(time.Time{})
131133// A direct hash calculation used for numeric and bool values.
132134func (w * walker ) hashDirect (v any ) (uint64 , error ) {
133135 w .h .Reset ()
134- err := binary .Write (w .h , binary .LittleEndian , v )
135- return w .h .Sum64 (), err
136+
137+ // Use direct byte manipulation for numbers instead of binary.Write to avoid allocations
138+ switch val := v .(type ) {
139+ case int64 :
140+ binary .LittleEndian .PutUint64 (w .buf [:8 ], uint64 (val ))
141+ w .h .Write (w .buf [:8 ])
142+ case uint64 :
143+ binary .LittleEndian .PutUint64 (w .buf [:8 ], val )
144+ w .h .Write (w .buf [:8 ])
145+ case int8 :
146+ w .buf [0 ] = byte (val )
147+ w .h .Write (w .buf [:1 ])
148+ case uint8 :
149+ w .buf [0 ] = val
150+ w .h .Write (w .buf [:1 ])
151+ case int16 :
152+ binary .LittleEndian .PutUint16 (w .buf [:2 ], uint16 (val ))
153+ w .h .Write (w .buf [:2 ])
154+ case uint16 :
155+ binary .LittleEndian .PutUint16 (w .buf [:2 ], val )
156+ w .h .Write (w .buf [:2 ])
157+ case int32 :
158+ binary .LittleEndian .PutUint32 (w .buf [:4 ], uint32 (val ))
159+ w .h .Write (w .buf [:4 ])
160+ case uint32 :
161+ binary .LittleEndian .PutUint32 (w .buf [:4 ], val )
162+ w .h .Write (w .buf [:4 ])
163+ case float32 :
164+ binary .LittleEndian .PutUint32 (w .buf [:4 ], math .Float32bits (val ))
165+ w .h .Write (w .buf [:4 ])
166+ case float64 :
167+ binary .LittleEndian .PutUint64 (w .buf [:8 ], math .Float64bits (val ))
168+ w .h .Write (w .buf [:8 ])
169+ case complex64 :
170+ binary .LittleEndian .PutUint32 (w .buf [:4 ], math .Float32bits (real (val )))
171+ binary .LittleEndian .PutUint32 (w .buf [4 :8 ], math .Float32bits (imag (val )))
172+ w .h .Write (w .buf [:8 ])
173+ case complex128 :
174+ binary .LittleEndian .PutUint64 (w .buf [:8 ], math .Float64bits (real (val )))
175+ binary .LittleEndian .PutUint64 (w .buf [8 :16 ], math .Float64bits (imag (val )))
176+ w .h .Write (w .buf [:16 ])
177+ default :
178+ // Fallback to binary.Write for unsupported types, for instance enums
179+ err := binary .Write (w .h , binary .LittleEndian , v )
180+ return w .h .Sum64 (), err
181+ }
182+
183+ return w .h .Sum64 (), nil
136184}
137185
138186// A direct hash calculation used for strings.
@@ -144,10 +192,12 @@ func (w *walker) hashString(s string) (uint64, error) {
144192func hashString (h hash.Hash64 , s string ) (uint64 , error ) {
145193 h .Reset ()
146194
147- // io.WriteString uses io.StringWriter if it exists, which is
148- // implemented by e.g. github.com/cespare/xxhash.
149- _ , err := io .WriteString (h , s )
150- return h .Sum64 (), err
195+ // Use zero-copy conversion from string to []byte using unsafe
196+ if len (s ) > 0 {
197+ b := unsafe .Slice (unsafe .StringData (s ), len (s ))
198+ h .Write (b )
199+ }
200+ return h .Sum64 (), nil
151201}
152202
153203func (w * walker ) visit (v reflect.Value , opts * visitOpts ) (uint64 , error ) {
@@ -181,23 +231,55 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
181231 }
182232
183233 if v .CanInt () {
184- if v .Kind () == reflect .Int {
185- // binary.Write requires a fixed-size value.
186- return w .hashDirect (v .Int ())
234+ i := v .Int ()
235+ switch v .Kind () {
236+ case reflect .Int :
237+ return w .hashDirect (i )
238+ case reflect .Int8 :
239+ return w .hashDirect (int8 (i ))
240+ case reflect .Int16 :
241+ return w .hashDirect (int16 (i ))
242+ case reflect .Int32 :
243+ return w .hashDirect (int32 (i ))
244+ case reflect .Int64 :
245+ return w .hashDirect (i )
187246 }
188- return w .hashDirect (v .Interface ())
189247 }
190248
191249 if v .CanUint () {
192- if v .Kind () == reflect .Uint {
193- // binary.Write requires a fixed-size value.
194- return w .hashDirect (v .Uint ())
250+ u := v .Uint ()
251+ switch v .Kind () {
252+ case reflect .Uint :
253+ return w .hashDirect (u )
254+ case reflect .Uint8 :
255+ return w .hashDirect (uint8 (u ))
256+ case reflect .Uint16 :
257+ return w .hashDirect (uint16 (u ))
258+ case reflect .Uint32 :
259+ return w .hashDirect (uint32 (u ))
260+ case reflect .Uint64 :
261+ return w .hashDirect (u )
195262 }
196- return w .hashDirect (v .Interface ())
197263 }
198264
199- if v .CanFloat () || v .CanComplex () {
200- return w .hashDirect (v .Interface ())
265+ if v .CanFloat () {
266+ f := v .Float ()
267+ switch v .Kind () {
268+ case reflect .Float32 :
269+ return w .hashDirect (float32 (f ))
270+ case reflect .Float64 :
271+ return w .hashDirect (f )
272+ }
273+ }
274+
275+ if v .CanComplex () {
276+ c := v .Complex ()
277+ switch v .Kind () {
278+ case reflect .Complex64 :
279+ return w .hashDirect (complex64 (c ))
280+ case reflect .Complex128 :
281+ return w .hashDirect (c )
282+ }
201283 }
202284
203285 k := v .Kind ()
@@ -218,8 +300,8 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
218300 return 0 , err
219301 }
220302
221- err = binary . Write ( w .h , binary . LittleEndian , b )
222- return w .h .Sum64 (), err
303+ w .h . Write ( b )
304+ return w .h .Sum64 (), nil
223305 }
224306
225307 switch k {
@@ -290,18 +372,10 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
290372 return h , nil
291373
292374 case reflect .Struct :
293- parent := v .Interface ()
294375 var include Includable
295- if impl , ok := parent .(Includable ); ok {
296- include = impl
297- }
298-
299- if impl , ok := parent .(Hashable ); ok {
300- return impl .Hash ()
301- }
376+ var parent interface {}
302377
303- // If we can address this value, check if the pointer value
304- // implements our interfaces and use that if so.
378+ // Check if we can address this value first (more common case for pointer receivers)
305379 if v .CanAddr () {
306380 vptr := v .Addr ()
307381 parentptr := vptr .Interface ()
@@ -312,18 +386,38 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
312386 if impl , ok := parentptr .(Hashable ); ok {
313387 return impl .Hash ()
314388 }
389+ // Only set parent if we'll need it for IncludableMap
390+ parent = parentptr
391+ }
392+
393+ // Only box the value if we haven't already found an implementation via pointer
394+ if include == nil && parent == nil {
395+ parent = v .Interface ()
396+ if impl , ok := parent .(Includable ); ok {
397+ include = impl
398+ }
399+
400+ if impl , ok := parent .(Hashable ); ok {
401+ return impl .Hash ()
402+ }
315403 }
316404
317405 t := v .Type ()
318- h , err := w .visit ( reflect . ValueOf ( t .Name ()), nil )
406+ h , err := w .hashString ( t .Name ())
319407 if err != nil {
320408 return 0 , err
321409 }
322410
323411 l := v .NumField ()
412+ var fieldOpts visitOpts
413+ // Defer boxing parent until we know we need it
414+ if parent == nil {
415+ parent = v .Interface ()
416+ }
417+ fieldOpts .Struct = parent
418+
324419 for i := 0 ; i < l ; i ++ {
325420 if innerV := v .Field (i ); v .CanSet () || t .Field (i ).Name != "_" {
326- var f visitFlag
327421 fieldType := t .Field (i )
328422 if fieldType .PkgPath != "" {
329423 // Unexported
@@ -366,21 +460,18 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
366460 }
367461 }
368462
369- switch tag {
370- case "set" :
371- f |= visitFlagSet
463+ fieldOpts . Flags = 0
464+ if tag == "set" {
465+ fieldOpts . Flags |= visitFlagSet
372466 }
373467
374- kh , err := w .visit ( reflect . ValueOf ( fieldType .Name ), nil )
468+ kh , err := w .hashString ( fieldType .Name )
375469 if err != nil {
376470 return 0 , err
377471 }
378472
379- vh , err := w .visit (innerV , & visitOpts {
380- Flags : f ,
381- Struct : parent ,
382- StructField : fieldType .Name ,
383- })
473+ fieldOpts .StructField = fieldType .Name
474+ vh , err := w .visit (innerV , & fieldOpts )
384475 if err != nil {
385476 return 0 , err
386477 }
@@ -435,16 +526,10 @@ func hashUpdateOrdered(h hash.Hash64, a, b uint64) uint64 {
435526 // For ordered updates, use a real hash function
436527 h .Reset ()
437528
438- // We just panic if the binary writes fail because we are writing
439- // an int64 which should never be fail-able.
440- e1 := binary .Write (h , binary .LittleEndian , a )
441- e2 := binary .Write (h , binary .LittleEndian , b )
442- if e1 != nil {
443- panic (e1 )
444- }
445- if e2 != nil {
446- panic (e2 )
447- }
529+ var buf [16 ]byte
530+ binary .LittleEndian .PutUint64 (buf [0 :8 ], a )
531+ binary .LittleEndian .PutUint64 (buf [8 :16 ], b )
532+ h .Write (buf [:])
448533
449534 return h .Sum64 ()
450535}
@@ -470,11 +555,9 @@ func hashUpdateUnordered(a, b uint64) uint64 {
470555func hashFinishUnordered (h hash.Hash64 , a uint64 ) uint64 {
471556 h .Reset ()
472557
473- // We just panic if the writes fail
474- e1 := binary .Write (h , binary .LittleEndian , a )
475- if e1 != nil {
476- panic (e1 )
477- }
558+ var buf [8 ]byte
559+ binary .LittleEndian .PutUint64 (buf [:], a )
560+ h .Write (buf [:])
478561
479562 return h .Sum64 ()
480563}
0 commit comments