Skip to content

Commit 36f30ba

Browse files
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Martin Möhrmann <[email protected]>
1 parent 671bcb5 commit 36f30ba

File tree

19 files changed

+540
-348
lines changed

19 files changed

+540
-348
lines changed

src/cmd/compile/internal/gc/alg.go

Lines changed: 164 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package gc
66

77
import (
88
"cmd/compile/internal/types"
9+
"cmd/internal/obj"
910
"fmt"
1011
)
1112

@@ -183,10 +184,82 @@ func algtype1(t *types.Type) (AlgKind, *types.Type) {
183184
return 0, nil
184185
}
185186

186-
// Generate a helper function to compute the hash of a value of type t.
187-
func genhash(sym *types.Sym, t *types.Type) {
187+
// genhash returns a symbol which is the closure used to compute
188+
// the hash of a value of type t.
189+
func genhash(t *types.Type) *obj.LSym {
190+
switch algtype(t) {
191+
default:
192+
// genhash is only called for types that have equality
193+
Fatalf("genhash %v", t)
194+
case AMEM0:
195+
return sysClosure("memhash0")
196+
case AMEM8:
197+
return sysClosure("memhash8")
198+
case AMEM16:
199+
return sysClosure("memhash16")
200+
case AMEM32:
201+
return sysClosure("memhash32")
202+
case AMEM64:
203+
return sysClosure("memhash64")
204+
case AMEM128:
205+
return sysClosure("memhash128")
206+
case ASTRING:
207+
return sysClosure("strhash")
208+
case AINTER:
209+
return sysClosure("interhash")
210+
case ANILINTER:
211+
return sysClosure("nilinterhash")
212+
case AFLOAT32:
213+
return sysClosure("f32hash")
214+
case AFLOAT64:
215+
return sysClosure("f64hash")
216+
case ACPLX64:
217+
return sysClosure("c64hash")
218+
case ACPLX128:
219+
return sysClosure("c128hash")
220+
case AMEM:
221+
// For other sizes of plain memory, we build a closure
222+
// that calls memhash_varlen. The size of the memory is
223+
// encoded in the first slot of the closure.
224+
closure := typeLookup(fmt.Sprintf(".hashfunc%d", t.Width)).Linksym()
225+
if len(closure.P) > 0 { // already generated
226+
return closure
227+
}
228+
if memhashvarlen == nil {
229+
memhashvarlen = sysfunc("memhash_varlen")
230+
}
231+
ot := 0
232+
ot = dsymptr(closure, ot, memhashvarlen, 0)
233+
ot = duintptr(closure, ot, uint64(t.Width)) // size encoded in closure
234+
ggloblsym(closure, int32(ot), obj.DUPOK|obj.RODATA)
235+
return closure
236+
case ASPECIAL:
237+
break
238+
}
239+
240+
closure := typesymprefix(".hashfunc", t).Linksym()
241+
if len(closure.P) > 0 { // already generated
242+
return closure
243+
}
244+
245+
// Generate hash functions for subtypes.
246+
// There are cases where we might not use these hashes,
247+
// but in that case they will get dead-code eliminated.
248+
// (And the closure generated by genhash will also get
249+
// dead-code eliminated, as we call the subtype hashers
250+
// directly.)
251+
switch t.Etype {
252+
case types.TARRAY:
253+
genhash(t.Elem())
254+
case types.TSTRUCT:
255+
for _, f := range t.FieldSlice() {
256+
genhash(f.Type)
257+
}
258+
}
259+
260+
sym := typesymprefix(".hash", t)
188261
if Debug['r'] != 0 {
189-
fmt.Printf("genhash %v %v\n", sym, t)
262+
fmt.Printf("genhash %v %v %v\n", closure, sym, t)
190263
}
191264

192265
lineno = autogeneratedPos // less confusing than end of input
@@ -204,13 +277,7 @@ func genhash(sym *types.Sym, t *types.Type) {
204277
np := asNode(tfn.Type.Params().Field(0).Nname)
205278
nh := asNode(tfn.Type.Params().Field(1).Nname)
206279

207-
// genhash is only called for types that have equality but
208-
// cannot be handled by the standard algorithms,
209-
// so t must be either an array or a struct.
210280
switch t.Etype {
211-
default:
212-
Fatalf("genhash %v", t)
213-
214281
case types.TARRAY:
215282
// An array of pure memory would be handled by the
216283
// standard algorithm, so the element type must not be
@@ -302,6 +369,13 @@ func genhash(sym *types.Sym, t *types.Type) {
302369

303370
fn.Func.SetNilCheckDisabled(true)
304371
funccompile(fn)
372+
373+
// Build closure. It doesn't close over any variables, so
374+
// it contains just the function pointer.
375+
dsymptr(closure, 0, sym.Linksym(), 0)
376+
ggloblsym(closure, int32(Widthptr), obj.DUPOK|obj.RODATA)
377+
378+
return closure
305379
}
306380

307381
func hashfor(t *types.Type) *Node {
@@ -325,6 +399,8 @@ func hashfor(t *types.Type) *Node {
325399
case ACPLX128:
326400
sym = Runtimepkg.Lookup("c128hash")
327401
default:
402+
// Note: the caller of hashfor ensured that this symbol
403+
// exists and has a body by calling genhash for t.
328404
sym = typesymprefix(".hash", t)
329405
}
330406

@@ -340,13 +416,82 @@ func hashfor(t *types.Type) *Node {
340416
return n
341417
}
342418

343-
// geneq generates a helper function to
344-
// check equality of two values of type t.
345-
func geneq(sym *types.Sym, t *types.Type) {
419+
// sysClosure returns a closure which will call the
420+
// given runtime function (with no closed-over variables).
421+
func sysClosure(name string) *obj.LSym {
422+
s := sysvar(name + "·f")
423+
if len(s.P) == 0 {
424+
f := sysfunc(name)
425+
dsymptr(s, 0, f, 0)
426+
ggloblsym(s, int32(Widthptr), obj.DUPOK|obj.RODATA)
427+
}
428+
return s
429+
}
430+
431+
// geneq returns a symbol which is the closure used to compute
432+
// equality for two objects of type t.
433+
func geneq(t *types.Type) *obj.LSym {
434+
switch algtype(t) {
435+
case ANOEQ:
436+
// The runtime will panic if it tries to compare
437+
// a type with a nil equality function.
438+
return nil
439+
case AMEM0:
440+
return sysClosure("memequal0")
441+
case AMEM8:
442+
return sysClosure("memequal8")
443+
case AMEM16:
444+
return sysClosure("memequal16")
445+
case AMEM32:
446+
return sysClosure("memequal32")
447+
case AMEM64:
448+
return sysClosure("memequal64")
449+
case AMEM128:
450+
return sysClosure("memequal128")
451+
case ASTRING:
452+
return sysClosure("strequal")
453+
case AINTER:
454+
return sysClosure("interequal")
455+
case ANILINTER:
456+
return sysClosure("nilinterequal")
457+
case AFLOAT32:
458+
return sysClosure("f32equal")
459+
case AFLOAT64:
460+
return sysClosure("f64equal")
461+
case ACPLX64:
462+
return sysClosure("c64equal")
463+
case ACPLX128:
464+
return sysClosure("c128equal")
465+
case AMEM:
466+
// make equality closure. The size of the type
467+
// is encoded in the closure.
468+
closure := typeLookup(fmt.Sprintf(".eqfunc%d", t.Width)).Linksym()
469+
if len(closure.P) != 0 {
470+
return closure
471+
}
472+
if memequalvarlen == nil {
473+
memequalvarlen = sysvar("memequal_varlen") // asm func
474+
}
475+
ot := 0
476+
ot = dsymptr(closure, ot, memequalvarlen, 0)
477+
ot = duintptr(closure, ot, uint64(t.Width))
478+
ggloblsym(closure, int32(ot), obj.DUPOK|obj.RODATA)
479+
return closure
480+
case ASPECIAL:
481+
break
482+
}
483+
484+
closure := typesymprefix(".eqfunc", t).Linksym()
485+
if len(closure.P) > 0 { // already generated
486+
return closure
487+
}
488+
sym := typesymprefix(".eq", t)
346489
if Debug['r'] != 0 {
347-
fmt.Printf("geneq %v %v\n", sym, t)
490+
fmt.Printf("geneq %v\n", t)
348491
}
349492

493+
// Autogenerate code for equality of structs and arrays.
494+
350495
lineno = autogeneratedPos // less confusing than end of input
351496
dclcontext = PEXTERN
352497

@@ -362,7 +507,7 @@ func geneq(sym *types.Sym, t *types.Type) {
362507
np := asNode(tfn.Type.Params().Field(0).Nname)
363508
nq := asNode(tfn.Type.Params().Field(1).Nname)
364509

365-
// geneq is only called for types that have equality but
510+
// We reach here only for types that have equality but
366511
// cannot be handled by the standard algorithms,
367512
// so t must be either an array or a struct.
368513
switch t.Etype {
@@ -481,6 +626,11 @@ func geneq(sym *types.Sym, t *types.Type) {
481626
// are shallow.
482627
fn.Func.SetNilCheckDisabled(true)
483628
funccompile(fn)
629+
630+
// Generate a closure which points at the function we just generated.
631+
dsymptr(closure, 0, sym.Linksym(), 0)
632+
ggloblsym(closure, int32(Widthptr), obj.DUPOK|obj.RODATA)
633+
return closure
484634
}
485635

486636
// eqfield returns the node

src/cmd/compile/internal/gc/builtin.go

Lines changed: 55 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/compile/internal/gc/builtin/runtime.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,34 @@ func memclrNoHeapPointers(ptr unsafe.Pointer, n uintptr)
179179
func memclrHasPointers(ptr unsafe.Pointer, n uintptr)
180180

181181
func memequal(x, y *any, size uintptr) bool
182+
func memequal0(x, y *any) bool
182183
func memequal8(x, y *any) bool
183184
func memequal16(x, y *any) bool
184185
func memequal32(x, y *any) bool
185186
func memequal64(x, y *any) bool
186187
func memequal128(x, y *any) bool
188+
func f32equal(p, q unsafe.Pointer) bool
189+
func f64equal(p, q unsafe.Pointer) bool
190+
func c64equal(p, q unsafe.Pointer) bool
191+
func c128equal(p, q unsafe.Pointer) bool
192+
func strequal(p, q unsafe.Pointer) bool
193+
func interequal(p, q unsafe.Pointer) bool
194+
func nilinterequal(p, q unsafe.Pointer) bool
195+
196+
func memhash(p unsafe.Pointer, h uintptr, size uintptr) uintptr
197+
func memhash0(p unsafe.Pointer, h uintptr) uintptr
198+
func memhash8(p unsafe.Pointer, h uintptr) uintptr
199+
func memhash16(p unsafe.Pointer, h uintptr) uintptr
200+
func memhash32(p unsafe.Pointer, h uintptr) uintptr
201+
func memhash64(p unsafe.Pointer, h uintptr) uintptr
202+
func memhash128(p unsafe.Pointer, h uintptr) uintptr
203+
func f32hash(p unsafe.Pointer, h uintptr) uintptr
204+
func f64hash(p unsafe.Pointer, h uintptr) uintptr
205+
func c64hash(p unsafe.Pointer, h uintptr) uintptr
206+
func c128hash(p unsafe.Pointer, h uintptr) uintptr
207+
func strhash(a unsafe.Pointer, h uintptr) uintptr
208+
func interhash(p unsafe.Pointer, h uintptr) uintptr
209+
func nilinterhash(p unsafe.Pointer, h uintptr) uintptr
187210

188211
// only used on 32-bit
189212
func int64div(int64, int64) int64

0 commit comments

Comments
 (0)