util/deephash: generate type-specific hasher funcs
name old time/op new time/op delta Hash-8 71.1µs ± 2% 71.5µs ± 1% ~ (p=0.114 n=9+8) HashPacketFilter-8 8.39µs ± 1% 4.83µs ± 2% -42.38% (p=0.000 n=8+9) HashMapAcyclic-8 56.2µs ± 1% 56.9µs ± 2% +1.17% (p=0.035 n=10+9) TailcfgNode-8 6.49µs ± 2% 3.54µs ± 1% -45.37% (p=0.000 n=9+9) HashArray-8 729ns ± 2% 566ns ± 3% -22.30% (p=0.000 n=10+10) name old alloc/op new alloc/op delta Hash-8 24.0B ± 0% 24.0B ± 0% ~ (all equal) HashPacketFilter-8 24.0B ± 0% 24.0B ± 0% ~ (all equal) HashMapAcyclic-8 0.00B 0.00B ~ (all equal) TailcfgNode-8 0.00B 0.00B ~ (all equal) HashArray-8 0.00B 0.00B ~ (all equal) name old allocs/op new allocs/op delta Hash-8 1.00 ± 0% 1.00 ± 0% ~ (all equal) HashPacketFilter-8 1.00 ± 0% 1.00 ± 0% ~ (all equal) HashMapAcyclic-8 0.00 0.00 ~ (all equal) TailcfgNode-8 0.00 0.00 ~ (all equal) HashArray-8 0.00 0.00 ~ (all equal) Change-Id: I34c4e786e748fe60280646d40cc63a2adb2ea6fe Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
4d0461f721
commit
2a22ea3e83
|
@ -26,6 +26,7 @@ import (
|
|||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash"
|
||||
"log"
|
||||
"math"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
@ -142,6 +143,35 @@ func Hash(v any) (s Sum) {
|
|||
return h.sum()
|
||||
}
|
||||
|
||||
// HasherForType is like Hash, but it returns a Hash func that's specialized for
|
||||
// the provided reflect type, avoiding a map lookup per value.
|
||||
func HasherForType[T any]() func(T) Sum {
|
||||
var zeroT T
|
||||
ti := getTypeInfo(reflect.TypeOf(zeroT))
|
||||
seedOnce.Do(initSeed)
|
||||
|
||||
return func(v T) Sum {
|
||||
h := hasherPool.Get().(*hasher)
|
||||
defer hasherPool.Put(h)
|
||||
h.reset()
|
||||
h.hashUint64(seed)
|
||||
|
||||
rv := reflect.ValueOf(v)
|
||||
|
||||
if rv.IsValid() {
|
||||
// Always treat the Hash input as an interface (it is), including hashing
|
||||
// its type, otherwise two Hash calls of different types could hash to the
|
||||
// same bytes off the different types and get equivalent Sum values. This is
|
||||
// the same thing that we do for reflect.Kind Interface in hashValue, but
|
||||
// the initial reflect.ValueOf from an interface value effectively strips
|
||||
// the interface box off so we have to do it at the top level by hand.
|
||||
h.hashType(rv.Type())
|
||||
h.hashValueWithType(rv, ti, false)
|
||||
}
|
||||
return h.sum()
|
||||
}
|
||||
}
|
||||
|
||||
// Update sets last to the hash of v and reports whether its value changed.
|
||||
func Update(last *Sum, v ...any) (changed bool) {
|
||||
sum := Hash(v)
|
||||
|
@ -170,14 +200,26 @@ func (h *hasher) hashUint32(i uint32) {
|
|||
binary.LittleEndian.PutUint32(h.scratch[:4], i)
|
||||
h.bw.Write(h.scratch[:4])
|
||||
}
|
||||
func (h *hasher) hashLen(n int) {
|
||||
binary.LittleEndian.PutUint64(h.scratch[:8], uint64(n))
|
||||
h.bw.Write(h.scratch[:8])
|
||||
}
|
||||
func (h *hasher) hashUint64(i uint64) {
|
||||
binary.LittleEndian.PutUint64(h.scratch[:8], i)
|
||||
h.bw.Write(h.scratch[:8])
|
||||
}
|
||||
|
||||
var uint8Type = reflect.TypeOf(byte(0))
|
||||
var (
|
||||
uint8Type = reflect.TypeOf(byte(0))
|
||||
timeTimeType = reflect.TypeOf(time.Time{})
|
||||
)
|
||||
|
||||
// typeInfo describes properties of a type.
|
||||
//
|
||||
// A non-nil typeInfo is populated into the typeHasher map
|
||||
// when its type is first requested, before its func is created.
|
||||
// Its func field fn is only populated once the type has been created.
|
||||
// This is used for recursive types.
|
||||
type typeInfo struct {
|
||||
rtype reflect.Type
|
||||
canMemHash bool
|
||||
|
@ -190,11 +232,394 @@ type typeInfo struct {
|
|||
// keyTypeInfo is the map key type's typeInfo.
|
||||
// It's set when rtype is of Kind Map.
|
||||
keyTypeInfo *typeInfo
|
||||
|
||||
hashFuncOnce sync.Once
|
||||
hashFuncLazy typeHasherFunc // nil until created
|
||||
}
|
||||
|
||||
// returns ok if it was handled; else slow path runs
|
||||
type typeHasherFunc func(h *hasher, v reflect.Value) (ok bool)
|
||||
|
||||
var typeInfoMap sync.Map // map[reflect.Type]*typeInfo
|
||||
var typeInfoMapPopulate sync.Mutex // just for adding to typeInfoMap
|
||||
|
||||
func (ti *typeInfo) hasher() typeHasherFunc {
|
||||
ti.hashFuncOnce.Do(ti.buildHashFuncOnce)
|
||||
return ti.hashFuncLazy
|
||||
}
|
||||
|
||||
func (ti *typeInfo) buildHashFuncOnce() {
|
||||
ti.hashFuncLazy = genTypeHasher(ti.rtype)
|
||||
}
|
||||
|
||||
func (h *hasher) hashBoolv(v reflect.Value) bool {
|
||||
var b byte
|
||||
if v.Bool() {
|
||||
b = 1
|
||||
}
|
||||
h.hashUint8(b)
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *hasher) hashUint8v(v reflect.Value) bool {
|
||||
h.hashUint8(uint8(v.Uint()))
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *hasher) hashInt8v(v reflect.Value) bool {
|
||||
h.hashUint8(uint8(v.Int()))
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *hasher) hashUint16v(v reflect.Value) bool {
|
||||
h.hashUint16(uint16(v.Uint()))
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *hasher) hashInt16v(v reflect.Value) bool {
|
||||
h.hashUint16(uint16(v.Int()))
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *hasher) hashUint32v(v reflect.Value) bool {
|
||||
h.hashUint32(uint32(v.Uint()))
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *hasher) hashInt32v(v reflect.Value) bool {
|
||||
h.hashUint32(uint32(v.Int()))
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *hasher) hashUint64v(v reflect.Value) bool {
|
||||
h.hashUint64(v.Uint())
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *hasher) hashInt64v(v reflect.Value) bool {
|
||||
h.hashUint64(uint64(v.Int()))
|
||||
return true
|
||||
}
|
||||
|
||||
func hashStructAppenderTo(h *hasher, v reflect.Value) bool {
|
||||
if !v.CanInterface() {
|
||||
return false // slow path
|
||||
}
|
||||
var a appenderTo
|
||||
if v.CanAddr() {
|
||||
a = v.Addr().Interface().(appenderTo)
|
||||
} else {
|
||||
a = v.Interface().(appenderTo)
|
||||
}
|
||||
size := h.scratch[:8]
|
||||
record := a.AppendTo(size)
|
||||
binary.LittleEndian.PutUint64(record, uint64(len(record)-len(size)))
|
||||
h.bw.Write(record)
|
||||
return true
|
||||
}
|
||||
|
||||
// hashPointerAppenderTo hashes v, a reflect.Ptr, that implements appenderTo.
|
||||
func hashPointerAppenderTo(h *hasher, v reflect.Value) bool {
|
||||
if !v.CanInterface() {
|
||||
return false // slow path
|
||||
}
|
||||
if v.IsNil() {
|
||||
h.hashUint8(0) // indicates nil
|
||||
return true
|
||||
}
|
||||
h.hashUint8(1) // indicates visiting a pointer
|
||||
a := v.Interface().(appenderTo)
|
||||
size := h.scratch[:8]
|
||||
record := a.AppendTo(size)
|
||||
binary.LittleEndian.PutUint64(record, uint64(len(record)-len(size)))
|
||||
h.bw.Write(record)
|
||||
return true
|
||||
}
|
||||
|
||||
// fieldInfo describes a struct field.
|
||||
type fieldInfo struct {
|
||||
index int // index of field for reflect.Value.Field(n)
|
||||
typeInfo *typeInfo
|
||||
canMemHash bool
|
||||
offset uintptr // when we can memhash the field
|
||||
size uintptr // when we can memhash the field
|
||||
}
|
||||
|
||||
// mergeContiguousFieldsCopy returns a copy of f with contiguous memhashable fields
|
||||
// merged together. Such fields get a bogus index and fu value.
|
||||
func mergeContiguousFieldsCopy(in []fieldInfo) []fieldInfo {
|
||||
ret := make([]fieldInfo, 0, len(in))
|
||||
var last *fieldInfo
|
||||
for _, f := range in {
|
||||
// Combine two fields if they're both contiguous & memhash-able.
|
||||
if f.canMemHash && last != nil && last.canMemHash && last.offset+last.size == f.offset {
|
||||
last.size += f.size
|
||||
last.index = -1
|
||||
last.typeInfo = nil
|
||||
} else {
|
||||
ret = append(ret, f)
|
||||
last = &ret[len(ret)-1]
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// genHashStructFields generates a typeHasherFunc for t, which must be of kind Struct.
|
||||
func genHashStructFields(t reflect.Type) typeHasherFunc {
|
||||
fields := make([]fieldInfo, 0, t.NumField())
|
||||
for i, n := 0, t.NumField(); i < n; i++ {
|
||||
sf := t.Field(i)
|
||||
if sf.Type.Size() == 0 {
|
||||
continue
|
||||
}
|
||||
fields = append(fields, fieldInfo{
|
||||
index: i,
|
||||
typeInfo: getTypeInfo(sf.Type),
|
||||
canMemHash: canMemHash(sf.Type),
|
||||
offset: sf.Offset,
|
||||
size: sf.Type.Size(),
|
||||
})
|
||||
}
|
||||
fieldsIfCanAddr := mergeContiguousFieldsCopy(fields)
|
||||
return structHasher{fields, fieldsIfCanAddr}.hash
|
||||
}
|
||||
|
||||
type structHasher struct {
|
||||
fields, fieldsIfCanAddr []fieldInfo
|
||||
}
|
||||
|
||||
func (sh structHasher) hash(h *hasher, v reflect.Value) bool {
|
||||
var base unsafe.Pointer
|
||||
if v.CanAddr() {
|
||||
base = v.Addr().UnsafePointer()
|
||||
for _, f := range sh.fieldsIfCanAddr {
|
||||
if f.canMemHash {
|
||||
h.bw.Write(unsafe.Slice((*byte)(unsafe.Pointer(uintptr(base)+f.offset)), f.size))
|
||||
} else if !f.typeInfo.hasher()(h, v.Field(f.index)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, f := range sh.fields {
|
||||
if !f.typeInfo.hasher()(h, v.Field(f.index)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// genHashPtrToMemoryRange returns a hasher where the reflect.Value is a Ptr to
|
||||
// the provided eleType.
|
||||
func genHashPtrToMemoryRange(eleType reflect.Type) typeHasherFunc {
|
||||
size := eleType.Size()
|
||||
return func(h *hasher, v reflect.Value) bool {
|
||||
if v.IsNil() {
|
||||
h.hashUint8(0) // indicates nil
|
||||
} else {
|
||||
h.hashUint8(1) // indicates visiting a pointer
|
||||
h.bw.Write(unsafe.Slice((*byte)(v.UnsafePointer()), size))
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const debug = false
|
||||
|
||||
func genTypeHasher(t reflect.Type) typeHasherFunc {
|
||||
if debug {
|
||||
log.Printf("generating func for %v", t)
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
return (*hasher).hashBoolv
|
||||
case reflect.Int8:
|
||||
return (*hasher).hashInt8v
|
||||
case reflect.Int16:
|
||||
return (*hasher).hashInt16v
|
||||
case reflect.Int32:
|
||||
return (*hasher).hashInt32v
|
||||
case reflect.Int, reflect.Int64:
|
||||
return (*hasher).hashInt64v
|
||||
case reflect.Uint8:
|
||||
return (*hasher).hashUint8v
|
||||
case reflect.Uint16:
|
||||
return (*hasher).hashUint16v
|
||||
case reflect.Uint32:
|
||||
return (*hasher).hashUint32v
|
||||
case reflect.Uint, reflect.Uintptr, reflect.Uint64:
|
||||
return (*hasher).hashUint64v
|
||||
case reflect.Float32:
|
||||
return (*hasher).hashFloat32v
|
||||
case reflect.Float64:
|
||||
return (*hasher).hashFloat64v
|
||||
case reflect.Complex64:
|
||||
return (*hasher).hashComplex64v
|
||||
case reflect.Complex128:
|
||||
return (*hasher).hashComplex128v
|
||||
case reflect.String:
|
||||
return (*hasher).hashString
|
||||
case reflect.Slice:
|
||||
et := t.Elem()
|
||||
if canMemHash(et) {
|
||||
return (*hasher).hashSliceMem
|
||||
}
|
||||
eti := getTypeInfo(et)
|
||||
return genHashSliceElements(eti)
|
||||
case reflect.Array:
|
||||
et := t.Elem()
|
||||
eti := getTypeInfo(et)
|
||||
return genHashArray(t, eti)
|
||||
case reflect.Struct:
|
||||
if t == timeTimeType {
|
||||
return (*hasher).hashTimev
|
||||
}
|
||||
if t.Implements(appenderToType) {
|
||||
return hashStructAppenderTo
|
||||
}
|
||||
return genHashStructFields(t)
|
||||
case reflect.Pointer:
|
||||
et := t.Elem()
|
||||
if canMemHash(et) {
|
||||
return genHashPtrToMemoryRange(et)
|
||||
}
|
||||
if t.Implements(appenderToType) {
|
||||
return hashPointerAppenderTo
|
||||
}
|
||||
if !typeIsRecursive(t) {
|
||||
eti := getTypeInfo(et)
|
||||
return func(h *hasher, v reflect.Value) bool {
|
||||
if v.IsNil() {
|
||||
h.hashUint8(0) // indicates nil
|
||||
return true
|
||||
}
|
||||
h.hashUint8(1) // indicates visiting a pointer
|
||||
return eti.hasher()(h, v.Elem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return func(h *hasher, v reflect.Value) bool {
|
||||
if debug {
|
||||
log.Printf("unhandled type %v", v.Type())
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// hashString hashes v, of kind String.
|
||||
func (h *hasher) hashString(v reflect.Value) bool {
|
||||
s := v.String()
|
||||
h.hashLen(len(s))
|
||||
h.bw.WriteString(s)
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *hasher) hashFloat32v(v reflect.Value) bool {
|
||||
h.hashUint32(math.Float32bits(float32(v.Float())))
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *hasher) hashFloat64v(v reflect.Value) bool {
|
||||
h.hashUint64(math.Float64bits(v.Float()))
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *hasher) hashComplex64v(v reflect.Value) bool {
|
||||
c := complex64(v.Complex())
|
||||
h.hashUint32(math.Float32bits(real(c)))
|
||||
h.hashUint32(math.Float32bits(imag(c)))
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *hasher) hashComplex128v(v reflect.Value) bool {
|
||||
c := v.Complex()
|
||||
h.hashUint64(math.Float64bits(real(c)))
|
||||
h.hashUint64(math.Float64bits(imag(c)))
|
||||
return true
|
||||
}
|
||||
|
||||
// hashString hashes v, of kind time.Time.
|
||||
func (h *hasher) hashTimev(v reflect.Value) bool {
|
||||
var t time.Time
|
||||
if v.CanAddr() {
|
||||
t = *(v.Addr().Interface().(*time.Time))
|
||||
} else {
|
||||
t = v.Interface().(time.Time)
|
||||
}
|
||||
b := t.AppendFormat(h.scratch[:1], time.RFC3339Nano)
|
||||
b[0] = byte(len(b) - 1) // more than sufficient width; if not, good enough.
|
||||
h.bw.Write(b)
|
||||
return true
|
||||
}
|
||||
|
||||
// hashSliceMem hashes v, of kind Slice, with a memhash-able element type.
|
||||
func (h *hasher) hashSliceMem(v reflect.Value) bool {
|
||||
vLen := v.Len()
|
||||
h.hashUint64(uint64(vLen))
|
||||
if vLen == 0 {
|
||||
return true
|
||||
}
|
||||
h.bw.Write(unsafe.Slice((*byte)(v.UnsafePointer()), v.Type().Elem().Size()*uintptr(vLen)))
|
||||
return true
|
||||
}
|
||||
|
||||
func genHashArrayMem(n int, arraySize uintptr, efu *typeInfo) typeHasherFunc {
|
||||
byElement := genHashArrayElements(n, efu)
|
||||
return func(h *hasher, v reflect.Value) bool {
|
||||
if v.CanAddr() {
|
||||
h.bw.Write(unsafe.Slice((*byte)(v.Addr().UnsafePointer()), arraySize))
|
||||
return true
|
||||
}
|
||||
return byElement(h, v)
|
||||
}
|
||||
}
|
||||
|
||||
func genHashArrayElements(n int, eti *typeInfo) typeHasherFunc {
|
||||
return func(h *hasher, v reflect.Value) bool {
|
||||
for i := 0; i < n; i++ {
|
||||
if !eti.hasher()(h, v.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func noopHasherFunc(h *hasher, v reflect.Value) bool { return true }
|
||||
|
||||
func genHashArray(t reflect.Type, eti *typeInfo) typeHasherFunc {
|
||||
if t.Size() == 0 {
|
||||
return noopHasherFunc
|
||||
}
|
||||
et := t.Elem()
|
||||
if canMemHash(et) {
|
||||
return genHashArrayMem(t.Len(), t.Size(), eti)
|
||||
}
|
||||
n := t.Len()
|
||||
return genHashArrayElements(n, eti)
|
||||
}
|
||||
|
||||
func genHashSliceElements(eti *typeInfo) typeHasherFunc {
|
||||
return sliceElementHasher{eti}.hash
|
||||
}
|
||||
|
||||
type sliceElementHasher struct {
|
||||
eti *typeInfo
|
||||
}
|
||||
|
||||
func (seh sliceElementHasher) hash(h *hasher, v reflect.Value) bool {
|
||||
vLen := v.Len()
|
||||
h.hashUint64(uint64(vLen))
|
||||
for i := 0; i < vLen; i++ {
|
||||
if !seh.eti.hasher()(h, v.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getTypeInfo(t reflect.Type) *typeInfo {
|
||||
if f, ok := typeInfoMap.Load(t); ok {
|
||||
return f.(*typeInfo)
|
||||
|
@ -353,6 +778,13 @@ func (h *hasher) hashValueWithType(v reflect.Value, ti *typeInfo, forceCycleChec
|
|||
w := h.bw
|
||||
doCheckCycles := forceCycleChecking || ti.isRecursive
|
||||
|
||||
if !doCheckCycles {
|
||||
hf := ti.hasher()
|
||||
if hf(h, v) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Generic handling.
|
||||
switch v.Kind() {
|
||||
default:
|
||||
|
|
|
@ -392,6 +392,238 @@ func TestCanMemHash(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetTypeHasher(t *testing.T) {
|
||||
switch runtime.GOARCH {
|
||||
case "amd64", "arm64", "arm", "386", "riscv64":
|
||||
default:
|
||||
// Test outputs below are specifically for little-endian machines.
|
||||
// Just skip everything else for now. Feel free to add more above if
|
||||
// you have the hardware to test and it's little-endian.
|
||||
t.Skipf("skipping on %v", runtime.GOARCH)
|
||||
}
|
||||
type typedString string
|
||||
var (
|
||||
someInt = int('A')
|
||||
someComplex128 = complex128(1 + 2i)
|
||||
someIP = netaddr.MustParseIP("1.2.3.4")
|
||||
)
|
||||
tests := []struct {
|
||||
name string
|
||||
val any
|
||||
want bool // set true automatically if out != ""
|
||||
out string
|
||||
out32 string // overwrites out if 32-bit
|
||||
}{
|
||||
{
|
||||
name: "int",
|
||||
val: int(1),
|
||||
out: "\x01\x00\x00\x00\x00\x00\x00\x00",
|
||||
},
|
||||
{
|
||||
name: "int_negative",
|
||||
val: int(-1),
|
||||
out: "\xff\xff\xff\xff\xff\xff\xff\xff",
|
||||
},
|
||||
{
|
||||
name: "int8",
|
||||
val: int8(1),
|
||||
out: "\x01",
|
||||
},
|
||||
{
|
||||
name: "float64",
|
||||
val: float64(1.0),
|
||||
out: "\x00\x00\x00\x00\x00\x00\xf0?",
|
||||
},
|
||||
{
|
||||
name: "float32",
|
||||
val: float32(1.0),
|
||||
out: "\x00\x00\x80?",
|
||||
},
|
||||
{
|
||||
name: "string",
|
||||
val: "foo",
|
||||
out: "\x03\x00\x00\x00\x00\x00\x00\x00foo",
|
||||
},
|
||||
{
|
||||
name: "typedString",
|
||||
val: typedString("foo"),
|
||||
out: "\x03\x00\x00\x00\x00\x00\x00\x00foo",
|
||||
},
|
||||
{
|
||||
name: "string_slice",
|
||||
val: []string{"foo", "bar"},
|
||||
out: "\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00foo\x03\x00\x00\x00\x00\x00\x00\x00bar",
|
||||
},
|
||||
{
|
||||
name: "int_slice",
|
||||
val: []int{1, 0, -1},
|
||||
out: "\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff",
|
||||
out32: "\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff",
|
||||
},
|
||||
{
|
||||
name: "struct",
|
||||
val: struct {
|
||||
a, b int
|
||||
c uint16
|
||||
}{1, -1, 2},
|
||||
out: "\x01\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x02\x00",
|
||||
},
|
||||
{
|
||||
name: "nil_int_ptr",
|
||||
val: (*int)(nil),
|
||||
out: "\x00",
|
||||
},
|
||||
{
|
||||
name: "int_ptr",
|
||||
val: &someInt,
|
||||
out: "\x01A\x00\x00\x00\x00\x00\x00\x00",
|
||||
out32: "\x01A\x00\x00\x00",
|
||||
},
|
||||
{
|
||||
name: "nil_uint32_ptr",
|
||||
val: (*uint32)(nil),
|
||||
out: "\x00",
|
||||
},
|
||||
{
|
||||
name: "complex128_ptr",
|
||||
val: &someComplex128,
|
||||
out: "\x01\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@",
|
||||
},
|
||||
{
|
||||
name: "packet_filter",
|
||||
val: filterRules,
|
||||
out: "\x04\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00*\v\x00\x00\x00\x00\x00\x00\x0010.1.3.4/32\v\x00\x00\x00\x00\x00\x00\x0010.0.0.0/24\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x001.2.3.4/32\x01 \x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x001.2.3.4/32\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00foo\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
out32: "\x04\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00*\v\x00\x00\x00\x00\x00\x00\x0010.1.3.4/32\v\x00\x00\x00\x00\x00\x00\x0010.0.0.0/24\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x001.2.3.4/32\x01 \x00\x00\x00\x01\x00\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x001.2.3.4/32\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00foo\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
},
|
||||
{
|
||||
name: "netaddr.IP",
|
||||
val: netaddr.MustParseIP("fe80::123%foo"),
|
||||
out: "\r\x00\x00\x00\x00\x00\x00\x00fe80::123%foo",
|
||||
},
|
||||
{
|
||||
name: "ptr-netaddr.IP",
|
||||
val: &someIP,
|
||||
out: "\x01\a\x00\x00\x00\x00\x00\x00\x001.2.3.4",
|
||||
},
|
||||
{
|
||||
name: "ptr-nil-netaddr.IP",
|
||||
val: (*netaddr.IP)(nil),
|
||||
out: "\x00",
|
||||
},
|
||||
{
|
||||
name: "time",
|
||||
val: time.Unix(0, 0).In(time.UTC),
|
||||
out: "\x141970-01-01T00:00:00Z",
|
||||
},
|
||||
{
|
||||
name: "time_custom_zone",
|
||||
val: time.Unix(1655311822, 0).In(time.FixedZone("FOO", -60*60)),
|
||||
out: "\x192022-06-15T15:50:22-01:00",
|
||||
},
|
||||
{
|
||||
name: "time_nil",
|
||||
val: (*time.Time)(nil),
|
||||
out: "\x00",
|
||||
},
|
||||
{
|
||||
name: "array_memhash",
|
||||
val: [4]byte{1, 2, 3, 4},
|
||||
out: "\x01\x02\x03\x04",
|
||||
},
|
||||
{
|
||||
name: "array_ptr_memhash",
|
||||
val: ptrTo([4]byte{1, 2, 3, 4}),
|
||||
out: "\x01\x01\x02\x03\x04",
|
||||
},
|
||||
{
|
||||
name: "ptr_to_struct_partially_memhashable",
|
||||
val: &struct {
|
||||
A int16
|
||||
B int16
|
||||
C *int
|
||||
}{5, 6, nil},
|
||||
out: "\x01\x05\x00\x06\x00\x00",
|
||||
},
|
||||
{
|
||||
name: "struct_partially_memhashable_but_cant_addr",
|
||||
val: struct {
|
||||
A int16
|
||||
B int16
|
||||
C *int
|
||||
}{5, 6, nil},
|
||||
out: "\x05\x00\x06\x00\x00",
|
||||
},
|
||||
{
|
||||
name: "array_elements",
|
||||
val: [4]byte{1, 2, 3, 4},
|
||||
out: "\x01\x02\x03\x04",
|
||||
},
|
||||
{
|
||||
name: "bool",
|
||||
val: true,
|
||||
out: "\x01",
|
||||
},
|
||||
{
|
||||
name: "IntIntByteInt",
|
||||
val: IntIntByteInt{1, 2, 3, 4},
|
||||
out: "\x01\x00\x00\x00\x02\x00\x00\x00\x03\x04\x00\x00\x00",
|
||||
},
|
||||
{
|
||||
name: "IntIntByteInt-canddr",
|
||||
val: &IntIntByteInt{1, 2, 3, 4},
|
||||
out: "\x01\x01\x00\x00\x00\x02\x00\x00\x00\x03\x04\x00\x00\x00",
|
||||
},
|
||||
{
|
||||
name: "array-IntIntByteInt",
|
||||
val: [2]IntIntByteInt{
|
||||
{1, 2, 3, 4},
|
||||
{5, 6, 7, 8},
|
||||
},
|
||||
out: "\x01\x00\x00\x00\x02\x00\x00\x00\x03\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\a\b\x00\x00\x00",
|
||||
},
|
||||
{
|
||||
name: "array-IntIntByteInt-canaddr",
|
||||
val: &[2]IntIntByteInt{
|
||||
{1, 2, 3, 4},
|
||||
{5, 6, 7, 8},
|
||||
},
|
||||
out: "\x01\x01\x00\x00\x00\x02\x00\x00\x00\x03\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\a\b\x00\x00\x00",
|
||||
},
|
||||
{
|
||||
name: "tailcfg.Node",
|
||||
val: &tailcfg.Node{},
|
||||
out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x140001-01-01T00:00:00Z\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x140001-01-01T00:00:00Z\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rv := reflect.ValueOf(tt.val)
|
||||
fn := getTypeInfo(rv.Type()).hasher()
|
||||
var buf bytes.Buffer
|
||||
h := &hasher{
|
||||
bw: bufio.NewWriter(&buf),
|
||||
}
|
||||
got := fn(h, rv)
|
||||
const ptrSize = 32 << uintptr(^uintptr(0)>>63)
|
||||
if tt.out32 != "" && ptrSize == 32 {
|
||||
tt.out = tt.out32
|
||||
}
|
||||
if tt.out != "" {
|
||||
tt.want = true
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Fatalf("func returned %v; want %v", got, tt.want)
|
||||
}
|
||||
if err := h.bw.Flush(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := buf.String(); got != tt.out {
|
||||
t.Fatalf("got %q; want %q", got, tt.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var sink = Hash("foo")
|
||||
|
||||
func BenchmarkHash(b *testing.B) {
|
||||
|
@ -448,8 +680,9 @@ var filterRules = []tailcfg.FilterRule{
|
|||
func BenchmarkHashPacketFilter(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
hash := HasherForType[[]tailcfg.FilterRule]()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sink = Hash(filterRules)
|
||||
sink = hash(filterRules)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue