From 4bc9badd759f863f784e5f8d105a25c615324359 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 17 Mar 2015 16:10:31 -0700 Subject: [PATCH] cmd/internal/gc: speed up large string switches Switch statements do a binary search on long runs of constants. Doing a less-than comparison on a string is much more expensive than on (say) an int. Use two part comparison for strings: First compare length, then the strings themselves. Benchmarks from issue 10000: benchmark old ns/op new ns/op delta BenchmarkIf0 3.36 3.35 -0.30% BenchmarkIf1 4.45 4.47 +0.45% BenchmarkIf2 5.22 5.26 +0.77% BenchmarkIf3 5.56 5.58 +0.36% BenchmarkIf4 10.5 10.6 +0.95% BenchmarkIfNewStr0 5.26 5.30 +0.76% BenchmarkIfNewStr1 7.19 7.15 -0.56% BenchmarkIfNewStr2 7.23 7.16 -0.97% BenchmarkIfNewStr3 7.47 7.43 -0.54% BenchmarkIfNewStr4 12.4 12.2 -1.61% BenchmarkSwitch0 9.56 4.24 -55.65% BenchmarkSwitch1 8.64 5.58 -35.42% BenchmarkSwitch2 9.38 10.1 +7.68% BenchmarkSwitch3 8.66 5.00 -42.26% BenchmarkSwitch4 7.99 8.18 +2.38% BenchmarkSwitchNewStr0 11.3 6.12 -45.84% BenchmarkSwitchNewStr1 11.1 8.33 -24.95% BenchmarkSwitchNewStr2 11.0 11.1 +0.91% BenchmarkSwitchNewStr3 10.3 6.93 -32.72% BenchmarkSwitchNewStr4 11.0 11.2 +1.82% Fixes #10000 Change-Id: Ia2fffc32e9843425374c274064f709ec7ee46d80 Reviewed-on: https://go-review.googlesource.com/7698 Reviewed-by: Keith Randall --- src/cmd/internal/gc/swt.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/cmd/internal/gc/swt.go b/src/cmd/internal/gc/swt.go index c1e0405fdce..991f3ac9042 100644 --- a/src/cmd/internal/gc/swt.go +++ b/src/cmd/internal/gc/swt.go @@ -314,7 +314,16 @@ func (s *exprSwitch) walkCases(cc []*caseClause) *Node { // find the middle and recur half := len(cc) / 2 a := Nod(OIF, nil, nil) - a.Ntest = Nod(OLE, s.exprname, cc[half-1].node.Left) + mid := cc[half-1].node.Left + le := Nod(OLE, s.exprname, mid) + if Isconst(mid, CTSTR) { + // Search by length and then by value; see exprcmp. + lenlt := Nod(OLT, Nod(OLEN, s.exprname, nil), Nod(OLEN, mid, nil)) + leneq := Nod(OEQ, Nod(OLEN, s.exprname, nil), Nod(OLEN, mid, nil)) + a.Ntest = Nod(OOROR, lenlt, Nod(OANDAND, leneq, le)) + } else { + a.Ntest = le + } typecheck(&a.Ntest, Erv) a.Nbody = list1(s.walkCases(cc[:half])) a.Nelse = list1(s.walkCases(cc[half:])) @@ -750,7 +759,19 @@ func exprcmp(c1, c2 *caseClause) int { case CTINT, CTRUNE: return Mpcmpfixfix(n1.Val.U.Xval, n2.Val.U.Xval) case CTSTR: - return cmpslit(n1, n2) + // Sort strings by length and then by value. + // It is much cheaper to compare lengths than values, + // and all we need here is consistency. + // We respect this sorting in exprSwitch.walkCases. + a := n1.Val.U.Sval + b := n2.Val.U.Sval + if len(a) < len(b) { + return -1 + } + if len(a) > len(b) { + return +1 + } + return stringsCompare(a, b) } return 0