aboutsummaryrefslogtreecommitdiff
path: root/src/reflect
diff options
context:
space:
mode:
authorKeith Randall <keithr@alum.mit.edu>2019-08-06 15:22:51 -0700
committerKeith Randall <khr@golang.org>2019-09-03 20:41:29 +0000
commit36f30ba289e31df033d100b2adb4eaf557f05a34 (patch)
tree17579106197e4c1d80b67cefdf9d8fdfd2ff2a2c /src/reflect
parent671bcb59666c37cb32b154c36aa91b29fdbf0835 (diff)
downloadgo-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.go94
-rw-r--r--src/reflect/value.go3
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.