diff options
author | Keith Randall <keithr@alum.mit.edu> | 2019-08-06 15:22:51 -0700 |
---|---|---|
committer | Keith Randall <khr@golang.org> | 2019-09-03 20:41:29 +0000 |
commit | 36f30ba289e31df033d100b2adb4eaf557f05a34 (patch) | |
tree | 17579106197e4c1d80b67cefdf9d8fdfd2ff2a2c /src/reflect | |
parent | 671bcb59666c37cb32b154c36aa91b29fdbf0835 (diff) | |
download | go-36f30ba289e31df033d100b2adb4eaf557f05a34.tar.gz go-36f30ba289e31df033d100b2adb4eaf557f05a34.zip |
cmd/compile,runtime: generate hash functions only for types which are map keys
Right now we generate hash functions for all types, just in case they
are used as map keys. That's a lot of wasted effort and binary size
for types which will never be used as a map key. Instead, generate
hash functions only for types that we know are map keys.
Just doing that is a bit too simple, since maps with an interface type
as a key might have to hash any concrete key type that implements that
interface. So for that case, implement hashing of such types at
runtime (instead of with generated code). It will be slower, but only
for maps with interface types as keys, and maybe only a bit slower as
the aeshash time probably dominates the dispatch time.
Reorg where we keep the equals and hash functions. Move the hash function
from the key type to the map type, saving a field in every non-map type.
That leaves only one function in the alg structure, so get rid of that and
just keep the equal function in the type descriptor itself.
cmd/go now has 10 generated hash functions, instead of 504. Makes
cmd/go 1.0% smaller. Update #6853.
Speed on non-interface keys is unchanged. Speed on interface keys
is ~20% slower:
name old time/op new time/op delta
MapInterfaceString-8 23.0ns ±21% 27.6ns ±14% +20.01% (p=0.002 n=10+10)
MapInterfacePtr-8 19.4ns ±16% 23.7ns ± 7% +22.48% (p=0.000 n=10+8)
Change-Id: I7c2e42292a46b5d4e288aaec4029bdbb01089263
Reviewed-on: https://go-review.googlesource.com/c/go/+/191198
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Martin Möhrmann <moehrmann@google.com>
Diffstat (limited to 'src/reflect')
-rw-r--r-- | src/reflect/type.go | 94 | ||||
-rw-r--r-- | src/reflect/value.go | 3 |
2 files changed, 37 insertions, 60 deletions
diff --git a/src/reflect/type.go b/src/reflect/type.go index b3df452ee8..5071394cbb 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -290,6 +290,10 @@ const ( // tflagNamed means the type has a name. tflagNamed tflag = 1 << 2 + + // tflagRegularMemory means that equal and hash functions can treat + // this type as a single region of t.size bytes. + tflagRegularMemory tflag = 1 << 3 ) // rtype is the common implementation of most values. @@ -298,26 +302,18 @@ const ( // rtype must be kept in sync with ../runtime/type.go:/^type._type. type rtype struct { size uintptr - ptrdata uintptr // number of bytes in the type that can contain pointers - hash uint32 // hash of type; avoids computation in hash tables - tflag tflag // extra type information flags - align uint8 // alignment of variable with this type - fieldAlign uint8 // alignment of struct field with this type - kind uint8 // enumeration for C - alg *typeAlg // algorithm table - gcdata *byte // garbage collection data - str nameOff // string form - ptrToThis typeOff // type for pointer to this type, may be zero -} - -// a copy of runtime.typeAlg -type typeAlg struct { - // function for hashing objects of this type - // (ptr to object, seed) -> hash - hash func(unsafe.Pointer, uintptr) uintptr + ptrdata uintptr // number of bytes in the type that can contain pointers + hash uint32 // hash of type; avoids computation in hash tables + tflag tflag // extra type information flags + align uint8 // alignment of variable with this type + fieldAlign uint8 // alignment of struct field with this type + kind uint8 // enumeration for C // function for comparing objects of this type // (ptr to object A, ptr to object B) -> ==? - equal func(unsafe.Pointer, unsafe.Pointer) bool + equal func(unsafe.Pointer, unsafe.Pointer) bool + gcdata *byte // garbage collection data + str nameOff // string form + ptrToThis typeOff // type for pointer to this type, may be zero } // Method on non-interface type @@ -397,9 +393,11 @@ type interfaceType struct { // mapType represents a map type. type mapType struct { rtype - key *rtype // map key type - elem *rtype // map element (value) type - bucket *rtype // internal bucket structure + key *rtype // map key type + elem *rtype // map element (value) type + bucket *rtype // internal bucket structure + // function for hashing keys (ptr to key, seed) -> hash + hasher func(unsafe.Pointer, uintptr) uintptr keysize uint8 // size of key slot valuesize uint8 // size of value slot bucketsize uint16 // size of bucket @@ -1457,7 +1455,7 @@ func (t *rtype) ConvertibleTo(u Type) bool { } func (t *rtype) Comparable() bool { - return t.alg != nil && t.alg.equal != nil + return t.equal != nil } // implements reports whether the type V implements the interface type T. @@ -1807,7 +1805,7 @@ func ChanOf(dir ChanDir, t Type) Type { var ichan interface{} = (chan unsafe.Pointer)(nil) prototype := *(**chanType)(unsafe.Pointer(&ichan)) ch := *prototype - ch.tflag = 0 + ch.tflag = tflagRegularMemory ch.dir = uintptr(dir) ch.str = resolveReflectName(newName(s, "", false)) ch.hash = fnv1(typ.hash, 'c', byte(dir)) @@ -1817,8 +1815,6 @@ func ChanOf(dir ChanDir, t Type) Type { return ti.(Type) } -func ismapkey(*rtype) bool // implemented in runtime - // MapOf returns the map type with the given key and element types. // For example, if k represents int and e represents string, // MapOf(k, e) represents map[int]string. @@ -1829,7 +1825,7 @@ func MapOf(key, elem Type) Type { ktyp := key.(*rtype) etyp := elem.(*rtype) - if !ismapkey(ktyp) { + if ktyp.equal == nil { panic("reflect.MapOf: invalid key type " + ktyp.String()) } @@ -1860,6 +1856,9 @@ func MapOf(key, elem Type) Type { mt.key = ktyp mt.elem = etyp mt.bucket = bucketOf(ktyp, etyp) + mt.hasher = func(p unsafe.Pointer, seed uintptr) uintptr { + return typehash(ktyp, p, seed) + } mt.flags = 0 if ktyp.size > maxKeySize { mt.keysize = uint8(ptrSize) @@ -2332,7 +2331,6 @@ func StructOf(fields []StructField) Type { size uintptr typalign uint8 comparable = true - hashable = true methods []method fs = make([]structField, len(fields)) @@ -2518,8 +2516,7 @@ func StructOf(fields []StructField) Type { repr = append(repr, ';') } - comparable = comparable && (ft.alg.equal != nil) - hashable = hashable && (ft.alg.hash != nil) + comparable = comparable && (ft.equal != nil) offset := align(size, uintptr(ft.align)) if ft.align > typalign { @@ -2634,7 +2631,7 @@ func StructOf(fields []StructField) Type { } typ.str = resolveReflectName(newName(str, "", false)) - typ.tflag = 0 + typ.tflag = 0 // TODO: set tflagRegularMemory typ.hash = hash typ.size = size typ.ptrdata = typeptrdata(typ.common()) @@ -2708,24 +2705,13 @@ func StructOf(fields []StructField) Type { typ.gcdata = &bv.data[0] } } - typ.alg = new(typeAlg) - if hashable { - typ.alg.hash = func(p unsafe.Pointer, seed uintptr) uintptr { - o := seed - for _, ft := range typ.fields { - pi := add(p, ft.offset(), "&x.field safe") - o = ft.typ.alg.hash(pi, o) - } - return o - } - } - + typ.equal = nil if comparable { - typ.alg.equal = func(p, q unsafe.Pointer) bool { + typ.equal = func(p, q unsafe.Pointer) bool { for _, ft := range typ.fields { pi := add(p, ft.offset(), "&x.field safe") qi := add(q, ft.offset(), "&x.field safe") - if !ft.typ.alg.equal(pi, qi) { + if !ft.typ.equal(pi, qi) { return false } } @@ -2826,7 +2812,7 @@ func ArrayOf(count int, elem Type) Type { var iarray interface{} = [1]unsafe.Pointer{} prototype := *(**arrayType)(unsafe.Pointer(&iarray)) array := *prototype - array.tflag = 0 + array.tflag = typ.tflag & tflagRegularMemory array.str = resolveReflectName(newName(s, "", false)) array.hash = fnv1(typ.hash, '[') for n := uint32(count); n > 0; n >>= 8 { @@ -2929,12 +2915,10 @@ func ArrayOf(count int, elem Type) Type { etyp := typ.common() esize := etyp.Size() - ealg := etyp.alg - array.alg = new(typeAlg) - if ealg.equal != nil { - eequal := ealg.equal - array.alg.equal = func(p, q unsafe.Pointer) bool { + array.equal = nil + if eequal := etyp.equal; eequal != nil { + array.equal = func(p, q unsafe.Pointer) bool { for i := 0; i < count; i++ { pi := arrayAt(p, i, esize, "i < count") qi := arrayAt(q, i, esize, "i < count") @@ -2946,16 +2930,6 @@ func ArrayOf(count int, elem Type) Type { return true } } - if ealg.hash != nil { - ehash := ealg.hash - array.alg.hash = func(ptr unsafe.Pointer, seed uintptr) uintptr { - o := seed - for i := 0; i < count; i++ { - o = ehash(arrayAt(ptr, i, esize, "i < count"), o) - } - return o - } - } switch { case count == 1 && !ifaceIndir(typ): diff --git a/src/reflect/value.go b/src/reflect/value.go index 9ea95bc1d9..2e80bfe77f 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -2765,6 +2765,9 @@ func typedmemclrpartial(t *rtype, ptr unsafe.Pointer, off, size uintptr) //go:noescape func typedslicecopy(elemType *rtype, dst, src sliceHeader) int +//go:noescape +func typehash(t *rtype, p unsafe.Pointer, h uintptr) uintptr + // Dummy annotation marking that the value x escapes, // for use in cases where the reflect code is so clever that // the compiler cannot follow. |