From: Keith Randall Date: Thu, 17 Aug 2023 21:20:21 +0000 (-0700) Subject: cmd/compile: use jump tables for large type switches X-Git-Tag: go1.22rc1~1129 X-Git-Url: http://www.git.cypherpunks.ru/?a=commitdiff_plain;h=47112996619da0683eb15e611a1e2df85416feee;p=gostls13.git cmd/compile: use jump tables for large type switches For large interface -> concrete type switches, we can use a jump table on some bits of the type hash instead of a binary search on the type hash. name old time/op new time/op delta SwitchTypePredictable-24 1.99ns ± 2% 1.78ns ± 5% -10.87% (p=0.000 n=10+10) SwitchTypeUnpredictable-24 11.0ns ± 1% 9.1ns ± 2% -17.55% (p=0.000 n=7+9) Change-Id: Ida4768e5d62c3ce1c2701288b72664aaa9e64259 Reviewed-on: https://go-review.googlesource.com/c/go/+/521497 Reviewed-by: Keith Randall Auto-Submit: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Cherry Mui Run-TryBot: Keith Randall --- diff --git a/src/cmd/compile/internal/test/switch_test.go b/src/cmd/compile/internal/test/switch_test.go index 30dee6257e..ddb39bfe5f 100644 --- a/src/cmd/compile/internal/test/switch_test.go +++ b/src/cmd/compile/internal/test/switch_test.go @@ -120,6 +120,48 @@ func benchmarkSwitchString(b *testing.B, predictable bool) { sink = n } +func BenchmarkSwitchTypePredictable(b *testing.B) { + benchmarkSwitchType(b, true) +} +func BenchmarkSwitchTypeUnpredictable(b *testing.B) { + benchmarkSwitchType(b, false) +} +func benchmarkSwitchType(b *testing.B, predictable bool) { + a := []any{ + int8(1), + int16(2), + int32(3), + int64(4), + uint8(5), + uint16(6), + uint32(7), + uint64(8), + } + n := 0 + rng := newRNG() + for i := 0; i < b.N; i++ { + rng = rng.next(predictable) + switch a[rng.value()&7].(type) { + case int8: + n += 1 + case int16: + n += 2 + case int32: + n += 3 + case int64: + n += 4 + case uint8: + n += 5 + case uint16: + n += 6 + case uint32: + n += 7 + case uint64: + n += 8 + } + } +} + // A simple random number generator used to make switches conditionally predictable. type rng uint64 diff --git a/src/cmd/compile/internal/walk/switch.go b/src/cmd/compile/internal/walk/switch.go index ebd3128251..67ccb2e5d1 100644 --- a/src/cmd/compile/internal/walk/switch.go +++ b/src/cmd/compile/internal/walk/switch.go @@ -7,6 +7,7 @@ package walk import ( "go/constant" "go/token" + "math/bits" "sort" "cmd/compile/internal/base" @@ -617,7 +618,9 @@ func (s *typeSwitch) flush() { } cc = merged - // TODO: figure out if we could use a jump table using some low bits of the type hashes. + if s.tryJumpTable(cc, &s.done) { + return + } binarySearch(len(cc), &s.done, func(i int) ir.Node { return ir.NewBinaryExpr(base.Pos, ir.OLE, s.hashname, ir.NewInt(base.Pos, int64(cc[i-1].hash))) @@ -632,6 +635,83 @@ func (s *typeSwitch) flush() { ) } +// Try to implement the clauses with a jump table. Returns true if successful. +func (s *typeSwitch) tryJumpTable(cc []typeClause, out *ir.Nodes) bool { + const minCases = 5 // have at least minCases cases in the switch + if base.Flag.N != 0 || !ssagen.Arch.LinkArch.CanJumpTable || base.Ctxt.Retpoline { + return false + } + if len(cc) < minCases { + return false // not enough cases for it to be worth it + } + hashes := make([]uint32, len(cc)) + // b = # of bits to use. Start with the minimum number of + // bits possible, but try a few larger sizes if needed. + b0 := bits.Len(uint(len(cc) - 1)) + for b := b0; b < b0+3; b++ { + pickI: + for i := 0; i <= 32-b; i++ { // starting bit position + // Compute the hash we'd get from all the cases, + // selecting b bits starting at bit i. + hashes = hashes[:0] + for _, c := range cc { + h := c.hash >> i & (1<> i & (1<