Skip to content

Commit c7ccbdd

Browse files
fengyoulingopherbot
authored andcommitted
cmd/compile/internal/ssa: more aggressive on dead auto elim
Propagate "unread" across OpMoves. If the addr of this auto is only used by an OpMove as its source arg, and the OpMove's target arg is the addr of another auto. If the 2nd auto can be eliminated, this one can also be eliminated. This CL eliminates unnecessary memory copies and makes the frame smaller in the following code snippet: func contains(m map[string][16]int, k string) bool { _, ok := m[k] return ok } These are the benchmark results followed by the benchmark code: goos: linux goarch: amd64 cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ Map1Access2Ok-8 9.582n ± 2% 9.226n ± 0% -3.72% (p=0.000 n=20) Map2Access2Ok-8 13.79n ± 1% 10.24n ± 1% -25.77% (p=0.000 n=20) Map3Access2Ok-8 68.68n ± 1% 12.65n ± 1% -81.58% (p=0.000 n=20) package main_test import "testing" var ( m1 = map[int]int{} m2 = map[int][16]int{} m3 = map[int][256]int{} ) func init() { for i := range 1000 { m1[i] = i m2[i] = [16]int{15:i} m3[i] = [256]int{255:i} } } func BenchmarkMap1Access2Ok(b *testing.B) { for i := range b.N { _, ok := m1[i%1000] if !ok { b.Errorf("%d not found", i) } } } func BenchmarkMap2Access2Ok(b *testing.B) { for i := range b.N { _, ok := m2[i%1000] if !ok { b.Errorf("%d not found", i) } } } func BenchmarkMap3Access2Ok(b *testing.B) { for i := range b.N { _, ok := m3[i%1000] if !ok { b.Errorf("%d not found", i) } } } Fixes #75398 Change-Id: If75e9caaa50d460efc31a94565b9ba28c8158771 Reviewed-on: https://go-review.googlesource.com/c/go/+/702875 Reviewed-by: Keith Randall <khr@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Keith Randall <khr@golang.org> Reviewed-by: Keith Randall <khr@google.com> Reviewed-by: Michael Pratt <mpratt@google.com>
1 parent 75b2bb1 commit c7ccbdd

File tree

3 files changed

+126
-20
lines changed

3 files changed

+126
-20
lines changed

src/cmd/compile/internal/ssa/deadstore.go

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,27 @@ func (sr shadowRange) merge(lo, hi int64) shadowRange {
203203
// reaches stores then we delete all the stores. The other operations will then
204204
// be eliminated by the dead code elimination pass.
205205
func elimDeadAutosGeneric(f *Func) {
206-
addr := make(map[*Value]*ir.Name) // values that the address of the auto reaches
207-
elim := make(map[*Value]*ir.Name) // values that could be eliminated if the auto is
208-
var used ir.NameSet // used autos that must be kept
206+
addr := make(map[*Value]*ir.Name) // values that the address of the auto reaches
207+
elim := make(map[*Value]*ir.Name) // values that could be eliminated if the auto is
208+
move := make(map[*ir.Name]ir.NameSet) // for a (Move &y &x _) and y is unused, move[y].Add(x)
209+
var used ir.NameSet // used autos that must be kept
210+
211+
// Adds a name to used and, when it is the target of a move, also
212+
// propagates the used state to its source.
213+
var usedAdd func(n *ir.Name) bool
214+
usedAdd = func(n *ir.Name) bool {
215+
if used.Has(n) {
216+
return false
217+
}
218+
used.Add(n)
219+
if s := move[n]; s != nil {
220+
delete(move, n)
221+
for n := range s {
222+
usedAdd(n)
223+
}
224+
}
225+
return true
226+
}
209227

210228
// visit the value and report whether any of the maps are updated
211229
visit := func(v *Value) (changed bool) {
@@ -244,10 +262,7 @@ func elimDeadAutosGeneric(f *Func) {
244262
if !ok || (n.Class != ir.PAUTO && !isABIInternalParam(f, n)) {
245263
return
246264
}
247-
if !used.Has(n) {
248-
used.Add(n)
249-
changed = true
250-
}
265+
changed = usedAdd(n) || changed
251266
return
252267
case OpStore, OpMove, OpZero:
253268
// v should be eliminated if we eliminate the auto.
@@ -279,10 +294,22 @@ func elimDeadAutosGeneric(f *Func) {
279294
if v.Type.IsMemory() || v.Type.IsFlags() || v.Op == OpPhi || v.MemoryArg() != nil {
280295
for _, a := range args {
281296
if n, ok := addr[a]; ok {
282-
if !used.Has(n) {
283-
used.Add(n)
284-
changed = true
297+
// If the addr of n is used by an OpMove as its source arg,
298+
// and the OpMove's target arg is the addr of a unused name,
299+
// then temporarily treat n as unused, and record in move map.
300+
if nam, ok := elim[v]; ok && v.Op == OpMove && !used.Has(nam) {
301+
if used.Has(n) {
302+
continue
303+
}
304+
s := move[nam]
305+
if s == nil {
306+
s = ir.NameSet{}
307+
move[nam] = s
308+
}
309+
s.Add(n)
310+
continue
285311
}
312+
changed = usedAdd(n) || changed
286313
}
287314
}
288315
return
@@ -291,17 +318,21 @@ func elimDeadAutosGeneric(f *Func) {
291318
// Propagate any auto addresses through v.
292319
var node *ir.Name
293320
for _, a := range args {
294-
if n, ok := addr[a]; ok && !used.Has(n) {
321+
if n, ok := addr[a]; ok {
295322
if node == nil {
296-
node = n
297-
} else if node != n {
323+
if !used.Has(n) {
324+
node = n
325+
}
326+
} else {
327+
if node == n {
328+
continue
329+
}
298330
// Most of the time we only see one pointer
299331
// reaching an op, but some ops can take
300332
// multiple pointers (e.g. NeqPtr, Phi etc.).
301333
// This is rare, so just propagate the first
302334
// value to keep things simple.
303-
used.Add(n)
304-
changed = true
335+
changed = usedAdd(n) || changed
305336
}
306337
}
307338
}
@@ -316,8 +347,7 @@ func elimDeadAutosGeneric(f *Func) {
316347
}
317348
if addr[v] != node {
318349
// This doesn't happen in practice, but catch it just in case.
319-
used.Add(node)
320-
changed = true
350+
changed = usedAdd(node) || changed
321351
}
322352
return
323353
}
@@ -336,9 +366,8 @@ func elimDeadAutosGeneric(f *Func) {
336366
}
337367
// keep the auto if its address reaches a control value
338368
for _, c := range b.ControlValues() {
339-
if n, ok := addr[c]; ok && !used.Has(n) {
340-
used.Add(n)
341-
changed = true
369+
if n, ok := addr[c]; ok {
370+
changed = usedAdd(n) || changed
342371
}
343372
}
344373
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package test
6+
7+
import "testing"
8+
9+
var (
10+
n = [16]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
11+
m = [16]int{2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32}
12+
)
13+
14+
func TestEqual(t *testing.T) {
15+
if r := move2(n, m, 0); r != n {
16+
t.Fatalf("%v != %v", r, n)
17+
}
18+
if r := move2(n, m, 1); r != m {
19+
t.Fatalf("%v != %v", r, m)
20+
}
21+
if r := move2p(n, m, 0); r != n {
22+
t.Fatalf("%v != %v", r, n)
23+
}
24+
if r := move2p(n, m, 1); r != m {
25+
t.Fatalf("%v != %v", r, m)
26+
}
27+
}
28+
29+
//go:noinline
30+
func move2(a, b [16]int, c int) [16]int {
31+
e := a
32+
f := b
33+
var d [16]int
34+
if c%2 == 0 {
35+
d = e
36+
} else {
37+
d = f
38+
}
39+
r := d
40+
return r
41+
}
42+
43+
//go:noinline
44+
func move2p(a, b [16]int, c int) [16]int {
45+
e := a
46+
f := b
47+
var p *[16]int
48+
if c%2 == 0 {
49+
p = &e
50+
} else {
51+
p = &f
52+
}
53+
r := *p
54+
return r
55+
}

test/codegen/maps.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,28 @@ func AccessString2(m map[string]int) bool {
3737
return ok
3838
}
3939

40+
func AccessStringIntArray2(m map[string][16]int, k string) bool {
41+
// amd64:-"MOVUPS"
42+
_, ok := m[k]
43+
return ok
44+
}
45+
46+
type Struct struct {
47+
A, B, C, D, E, F, G, H, I, J int
48+
}
49+
50+
func AccessStringStruct2(m map[string]Struct, k string) bool {
51+
// amd64:-"MOVUPS"
52+
_, ok := m[k]
53+
return ok
54+
}
55+
56+
func AccessIntArrayLarge2(m map[int][512]int, k int) bool {
57+
// amd64:-"REP",-"MOVSQ"
58+
_, ok := m[k]
59+
return ok
60+
}
61+
4062
// ------------------- //
4163
// String Conversion //
4264
// ------------------- //

0 commit comments

Comments
 (0)