cmd/compile/internal/ssa: more aggressive on dead auto elim
Updates #75398
diff --git a/src/cmd/compile/internal/ir/node.go b/src/cmd/compile/internal/ir/node.go
index 003ec15..151e699 100644
--- a/src/cmd/compile/internal/ir/node.go
+++ b/src/cmd/compile/internal/ir/node.go
@@ -426,6 +426,14 @@
(*s)[n] = struct{}{}
}
+// Del deletes n from s.
+func (s *NameSet) Del(n *Name) {
+ if s == nil {
+ return
+ }
+ delete(*s, n)
+}
+
type PragmaFlag uint16
const (
diff --git a/src/cmd/compile/internal/ssa/deadstore.go b/src/cmd/compile/internal/ssa/deadstore.go
index 9e67e83..a2a7766 100644
--- a/src/cmd/compile/internal/ssa/deadstore.go
+++ b/src/cmd/compile/internal/ssa/deadstore.go
@@ -202,9 +202,10 @@
// reaches stores then we delete all the stores. The other operations will then
// be eliminated by the dead code elimination pass.
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
- var used ir.NameSet // used autos that must be kept
+ 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
+ rely := make(map[*ir.Name]*ir.Name) // keys rely on values to be alived
+ var used ir.NameSet // used autos that must be kept
// visit the value and report whether any of the maps are updated
visit := func(v *Value) (changed bool) {
@@ -247,6 +248,7 @@
used.Add(n)
changed = true
}
+ rely[n] = nil
return
case OpStore, OpMove, OpZero:
// v should be eliminated if we eliminate the auto.
@@ -282,6 +284,15 @@
used.Add(n)
changed = true
}
+ var name *ir.Name
+ if nm, ok := elim[v]; ok && v.Op == OpMove {
+ name = nm
+ }
+ if o, ok := rely[n]; ok && o != name {
+ rely[n] = nil
+ } else if !ok {
+ rely[n] = name
+ }
}
}
return
@@ -302,6 +313,7 @@
used.Add(n)
changed = true
}
+ rely[n] = nil
}
}
if node == nil {
@@ -346,6 +358,18 @@
}
}
+ for range 4 {
+ changed := false
+ for n, o := range rely {
+ if o != nil && used.Has(n) && !used.Has(o) {
+ used.Del(n)
+ changed = true
+ }
+ }
+ if !changed {
+ break
+ }
+ }
// Eliminate stores to unread autos.
for v, n := range elim {
if used.Has(n) {
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Interesting idea. I'm not entirely sure why this is safe. I think it needs more comments as to how `rely` and `elim` interact, and how we're only throwing away autos that we are sure are never used.
rely := make(map[*ir.Name]*ir.Name) // keys rely on values to be keptI don't understand this comment. Maybe make it multiline and put a more detailed description?
rely[x] = y if a use of x depends on y being available.
rely[x] = nil means x must be available.
// Relying on just one name, or setting value to nil,Does this not depend on copy direction? It seems this code is the same whether n is from v.Args[0] (the destination) or v.Args[1] (the source).
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
// Del deletes n from s.Youlin Fengif it is present
Done
rely := make(map[*ir.Name]*ir.Name) // keys rely on values to be keptI don't understand this comment. Maybe make it multiline and put a more detailed description?
rely[x] = y if a use of x depends on y being available.
rely[x] = nil means x must be available.
Done
args = args[1:]Here we have a slice operation that removes the destination arg.
Does this not depend on copy direction? It seems this code is the same whether n is from v.Args[0] (the destination) or v.Args[1] (the source).
In the switch statement above, we have a slice operation on args that removes the destination arg. Since we are only interested in OpMove, maybe this would be ok?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Interesting idea. I'm not entirely sure why this is safe. I think it needs more comments as to how `rely` and `elim` interact, and how we're only throwing away autos that we are sure are never used.
Before this CL, `elimDeadAutosGeneric` already can eliminate the autos that are never readed, I mean it can make sure which auto is never readed, so we can use the same method later to ensure that a auto is only read once. If we have `(Move &y &x _)`, and `y` is never readed, and `x` is readed only by this `Move`. When `elimDeadAutosGeneric` returns, both the `Move` and `y` were eliminated, then `x` becomes never readed. So the only thing that we need to do is to find `x` and make sure it is only readed by the `Move` that is going to be eliminated (ensured by `rely`), then treat it as never readed before performing the elimination (by delete it from `used`). Am I missing something?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
I feel like this is overly complicated.
How about a possible simplification idea: We could instead treat moves as not uses. Then keep a map:
```
// There is a move that copies from moveSource[x] to x.
moveSource [*ir.Name]*ir.Name
```
Then, when we add `x` to `used`, we also add `moveSource[x]` to `used` (and recursively, if needed).
// and the name's class is also ir.PAUTO.This is not checked here. Does it matter?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
I feel like this is overly complicated.
How about a possible simplification idea: We could instead treat moves as not uses. Then keep a map:
```
// There is a move that copies from moveSource[x] to x.
moveSource [*ir.Name]*ir.Name
```Then, when we add `x` to `used`, we also add `moveSource[x]` to `used` (and recursively, if needed).
Good idea. I'll have a try.
// and the name's class is also ir.PAUTO.This is not checked here. Does it matter?
I think I don't need to bother here, the value is already checked before adding it to `addr` or `elim`.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
Youlin FengI feel like this is overly complicated.
How about a possible simplification idea: We could instead treat moves as not uses. Then keep a map:
```
// There is a move that copies from moveSource[x] to x.
moveSource [*ir.Name]*ir.Name
```Then, when we add `x` to `used`, we also add `moveSource[x]` to `used` (and recursively, if needed).
Good idea. I'll have a try.
Done.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
PTAL when it is convenient.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Thanks, this version looks a lot simpler.
// 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 name, thenI see no distinction between source and target here. How does that work?
move[nam] = append(move[nam], n)If we loop multiple times in the iteration loop below, will we end up adding the same entry multiple times?
// Propagate "used" by OpMoves.Maybe this separate loop isn't necessary? If we had
```
var usedAdd func(n *ir.Name)
usedAdd = func(n *ir.Name) {
used.Add(n)
for _, source := range move[n] {
usedAdd(source)
}
}
```
Then we can replace all calls of `used.Add(n)` with `usedAdd(n)`.
(And maybe we can put the `!used.Has(n)` and `changed = true` parts in here also.)
Then the iterate-until-converged loop we already have would do this for us.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
// 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 name, thenI see no distinction between source and target here. How does that work?
There is a slice operation on `args` in the upper switch statement when v.Op is OpMove, which removed `arg[0]`.
If we loop multiple times in the iteration loop below, will we end up adding the same entry multiple times?
Yes. So, I changed the value type of the `move` map to *ir.NameSet.
Maybe this separate loop isn't necessary? If we had
```
var usedAdd func(n *ir.Name)
usedAdd = func(n *ir.Name) {
used.Add(n)
for _, source := range move[n] {
usedAdd(source)
}
}
```
Then we can replace all calls of `used.Add(n)` with `usedAdd(n)`.(And maybe we can put the `!used.Has(n)` and `changed = true` parts in here also.)
Then the iterate-until-converged loop we already have would do this for us.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Code looks good. I think we do need some correctness tests, though. That we keep the source of a move in various situations. For instance, when we use a move's result, but the source of that move is a phi, make sure that both the phi's sources are marked used as well.
if !s.Has(n) {You don't need this guard. `Add` will do nothing if `n` is already in `s`.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
Code looks good. I think we do need some correctness tests, though. That we keep the source of a move in various situations. For instance, when we use a move's result, but the source of that move is a phi, make sure that both the phi's sources are marked used as well.
Added some.
You don't need this guard. `Add` will do nothing if `n` is already in `s`.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Youlin FengCode looks good. I think we do need some correctness tests, though. That we keep the source of a move in various situations. For instance, when we use a move's result, but the source of that move is a phi, make sure that both the phi's sources are marked used as well.
Added some.
It looks like you added some more optimization tests, that we do apply the optimization when it is safe.
What I want is tests that make sure we don't do the optimization when it isn't safe to do.
Those wouldn't be test/codegen tests, but probably something in cmd/compile/internal/test.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Youlin FengCode looks good. I think we do need some correctness tests, though. That we keep the source of a move in various situations. For instance, when we use a move's result, but the source of that move is a phi, make sure that both the phi's sources are marked used as well.
Keith RandallAdded some.
It looks like you added some more optimization tests, that we do apply the optimization when it is safe.
What I want is tests that make sure we don't do the optimization when it isn't safe to do.
Those wouldn't be test/codegen tests, but probably something in cmd/compile/internal/test.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +2 |
move := make(map[*ir.Name]*ir.NameSet) // for a (Move &y &x _) and y is unused, move[y].Add(x)I think this should just be a `ir.NameSet`, not a `*ir.NameSet`.
s = new(ir.NameSet)Then this is just s = ir.NameSet{}
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
move := make(map[*ir.Name]*ir.NameSet) // for a (Move &y &x _) and y is unused, move[y].Add(x)I think this should just be a `ir.NameSet`, not a `*ir.NameSet`.
Done
Then this is just s = ir.NameSet{}
Done
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
PTAL.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
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
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |