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>
This commit is contained in:
Youlin Feng 2025-09-11 23:57:38 +08:00 committed by Gopher Robot
parent 75b2bb1d1a
commit c7ccbddf22
3 changed files with 126 additions and 20 deletions

View file

@ -205,8 +205,26 @@ func (sr shadowRange) merge(lo, hi int64) shadowRange {
func elimDeadAutosGeneric(f *Func) {
addr := make(map[*Value]*ir.Name) // values that the address of the auto reaches
elim := make(map[*Value]*ir.Name) // values that could be eliminated if the auto is
move := make(map[*ir.Name]ir.NameSet) // for a (Move &y &x _) and y is unused, move[y].Add(x)
var used ir.NameSet // used autos that must be kept
// Adds a name to used and, when it is the target of a move, also
// propagates the used state to its source.
var usedAdd func(n *ir.Name) bool
usedAdd = func(n *ir.Name) bool {
if used.Has(n) {
return false
}
used.Add(n)
if s := move[n]; s != nil {
delete(move, n)
for n := range s {
usedAdd(n)
}
}
return true
}
// visit the value and report whether any of the maps are updated
visit := func(v *Value) (changed bool) {
args := v.Args
@ -244,10 +262,7 @@ func elimDeadAutosGeneric(f *Func) {
if !ok || (n.Class != ir.PAUTO && !isABIInternalParam(f, n)) {
return
}
if !used.Has(n) {
used.Add(n)
changed = true
}
changed = usedAdd(n) || changed
return
case OpStore, OpMove, OpZero:
// v should be eliminated if we eliminate the auto.
@ -279,10 +294,22 @@ func elimDeadAutosGeneric(f *Func) {
if v.Type.IsMemory() || v.Type.IsFlags() || v.Op == OpPhi || v.MemoryArg() != nil {
for _, a := range args {
if n, ok := addr[a]; ok {
if !used.Has(n) {
used.Add(n)
changed = true
// If the addr of n is used by an OpMove as its source arg,
// and the OpMove's target arg is the addr of a unused name,
// then temporarily treat n as unused, and record in move map.
if nam, ok := elim[v]; ok && v.Op == OpMove && !used.Has(nam) {
if used.Has(n) {
continue
}
s := move[nam]
if s == nil {
s = ir.NameSet{}
move[nam] = s
}
s.Add(n)
continue
}
changed = usedAdd(n) || changed
}
}
return
@ -291,17 +318,21 @@ func elimDeadAutosGeneric(f *Func) {
// Propagate any auto addresses through v.
var node *ir.Name
for _, a := range args {
if n, ok := addr[a]; ok && !used.Has(n) {
if n, ok := addr[a]; ok {
if node == nil {
if !used.Has(n) {
node = n
} else if node != n {
}
} else {
if node == n {
continue
}
// Most of the time we only see one pointer
// reaching an op, but some ops can take
// multiple pointers (e.g. NeqPtr, Phi etc.).
// This is rare, so just propagate the first
// value to keep things simple.
used.Add(n)
changed = true
changed = usedAdd(n) || changed
}
}
}
@ -316,8 +347,7 @@ func elimDeadAutosGeneric(f *Func) {
}
if addr[v] != node {
// This doesn't happen in practice, but catch it just in case.
used.Add(node)
changed = true
changed = usedAdd(node) || changed
}
return
}
@ -336,9 +366,8 @@ func elimDeadAutosGeneric(f *Func) {
}
// keep the auto if its address reaches a control value
for _, c := range b.ControlValues() {
if n, ok := addr[c]; ok && !used.Has(n) {
used.Add(n)
changed = true
if n, ok := addr[c]; ok {
changed = usedAdd(n) || changed
}
}
}

View file

@ -0,0 +1,55 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package test
import "testing"
var (
n = [16]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
m = [16]int{2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32}
)
func TestEqual(t *testing.T) {
if r := move2(n, m, 0); r != n {
t.Fatalf("%v != %v", r, n)
}
if r := move2(n, m, 1); r != m {
t.Fatalf("%v != %v", r, m)
}
if r := move2p(n, m, 0); r != n {
t.Fatalf("%v != %v", r, n)
}
if r := move2p(n, m, 1); r != m {
t.Fatalf("%v != %v", r, m)
}
}
//go:noinline
func move2(a, b [16]int, c int) [16]int {
e := a
f := b
var d [16]int
if c%2 == 0 {
d = e
} else {
d = f
}
r := d
return r
}
//go:noinline
func move2p(a, b [16]int, c int) [16]int {
e := a
f := b
var p *[16]int
if c%2 == 0 {
p = &e
} else {
p = &f
}
r := *p
return r
}

View file

@ -37,6 +37,28 @@ func AccessString2(m map[string]int) bool {
return ok
}
func AccessStringIntArray2(m map[string][16]int, k string) bool {
// amd64:-"MOVUPS"
_, ok := m[k]
return ok
}
type Struct struct {
A, B, C, D, E, F, G, H, I, J int
}
func AccessStringStruct2(m map[string]Struct, k string) bool {
// amd64:-"MOVUPS"
_, ok := m[k]
return ok
}
func AccessIntArrayLarge2(m map[int][512]int, k int) bool {
// amd64:-"REP",-"MOVSQ"
_, ok := m[k]
return ok
}
// ------------------- //
// String Conversion //
// ------------------- //