mirror of
https://github.com/golang/go
synced 2024-11-26 12:27:57 -07:00
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 <khr@google.com> Auto-Submit: Keith Randall <khr@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Cherry Mui <cherryyz@google.com> Run-TryBot: Keith Randall <khr@golang.org>
This commit is contained in:
parent
556e9c5f3e
commit
4711299661
@ -120,6 +120,48 @@ func benchmarkSwitchString(b *testing.B, predictable bool) {
|
|||||||
sink = n
|
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.
|
// A simple random number generator used to make switches conditionally predictable.
|
||||||
type rng uint64
|
type rng uint64
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ package walk
|
|||||||
import (
|
import (
|
||||||
"go/constant"
|
"go/constant"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"math/bits"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"cmd/compile/internal/base"
|
"cmd/compile/internal/base"
|
||||||
@ -617,7 +618,9 @@ func (s *typeSwitch) flush() {
|
|||||||
}
|
}
|
||||||
cc = merged
|
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,
|
binarySearch(len(cc), &s.done,
|
||||||
func(i int) ir.Node {
|
func(i int) ir.Node {
|
||||||
return ir.NewBinaryExpr(base.Pos, ir.OLE, s.hashname, ir.NewInt(base.Pos, int64(cc[i-1].hash)))
|
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<<b - 1)
|
||||||
|
hashes = append(hashes, h)
|
||||||
|
}
|
||||||
|
// Order by increasing hash.
|
||||||
|
sort.Slice(hashes, func(j, k int) bool {
|
||||||
|
return hashes[j] < hashes[k]
|
||||||
|
})
|
||||||
|
for j := 1; j < len(hashes); j++ {
|
||||||
|
if hashes[j] == hashes[j-1] {
|
||||||
|
// There is a duplicate hash; try a different b/i pair.
|
||||||
|
continue pickI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All hashes are distinct. Use these values of b and i.
|
||||||
|
h := s.hashname
|
||||||
|
if i != 0 {
|
||||||
|
h = ir.NewBinaryExpr(base.Pos, ir.ORSH, h, ir.NewInt(base.Pos, int64(i)))
|
||||||
|
}
|
||||||
|
h = ir.NewBinaryExpr(base.Pos, ir.OAND, h, ir.NewInt(base.Pos, int64(1<<b-1)))
|
||||||
|
h = typecheck.Expr(h)
|
||||||
|
|
||||||
|
// Build jump table.
|
||||||
|
jt := ir.NewJumpTableStmt(base.Pos, h)
|
||||||
|
jt.Cases = make([]constant.Value, 1<<b)
|
||||||
|
jt.Targets = make([]*types.Sym, 1<<b)
|
||||||
|
out.Append(jt)
|
||||||
|
|
||||||
|
// Start with all hashes going to the didn't-match target.
|
||||||
|
noMatch := typecheck.AutoLabel(".s")
|
||||||
|
for j := 0; j < 1<<b; j++ {
|
||||||
|
jt.Cases[j] = constant.MakeInt64(int64(j))
|
||||||
|
jt.Targets[j] = noMatch
|
||||||
|
}
|
||||||
|
// This statement is not reachable, but it will make it obvious that we don't
|
||||||
|
// fall through to the first case.
|
||||||
|
out.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, noMatch))
|
||||||
|
|
||||||
|
// Emit each of the actual cases.
|
||||||
|
for _, c := range cc {
|
||||||
|
h := c.hash >> i & (1<<b - 1)
|
||||||
|
label := typecheck.AutoLabel(".s")
|
||||||
|
jt.Targets[h] = label
|
||||||
|
out.Append(ir.NewLabelStmt(base.Pos, label))
|
||||||
|
out.Append(c.body...)
|
||||||
|
// We reach here if the hash matches but the type equality test fails.
|
||||||
|
out.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, noMatch))
|
||||||
|
}
|
||||||
|
// Emit point to go to if type doesn't match any case.
|
||||||
|
out.Append(ir.NewLabelStmt(base.Pos, noMatch))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Couldn't find a perfect hash. Fall back to binary search.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// binarySearch constructs a binary search tree for handling n cases,
|
// binarySearch constructs a binary search tree for handling n cases,
|
||||||
// and appends it to out. It's used for efficiently implementing
|
// and appends it to out. It's used for efficiently implementing
|
||||||
// switch statements.
|
// switch statements.
|
||||||
|
@ -99,3 +99,22 @@ func mimetype(ext string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use jump tables for type switches to concrete types.
|
||||||
|
func typeSwitch(x any) int {
|
||||||
|
// amd64:`JMP\s\(.*\)\(.*\)$`
|
||||||
|
// arm64:`MOVD\s\(R.*\)\(R.*<<3\)`,`JMP\s\(R.*\)$`
|
||||||
|
switch x.(type) {
|
||||||
|
case int:
|
||||||
|
return 0
|
||||||
|
case int8:
|
||||||
|
return 1
|
||||||
|
case int16:
|
||||||
|
return 2
|
||||||
|
case int32:
|
||||||
|
return 3
|
||||||
|
case int64:
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
return 7
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user