Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NewOptimizer] Better handling in the presence of select value #26969

Closed
wants to merge 3 commits into from

Conversation

Keno
Copy link
Member

@Keno Keno commented May 3, 2018

The benchmarks contain code like this:

x::Union{Nothing, Int}
result += ifelse(x === nothing, 0, x)

which, perhaps somewhat ironically is quite a bit harder
on the new optimizer than an equivalent code sequence
using ternary operators. The reason for this is that
ifelse gets inferred as Union{Int, Nothing}, creating
a phi node of that type, which then causes a union split +
that the optimizer can't really get rid of easily. What this
commit does is add some local improvements to help with the
situation. First, it adds some minimal back inference during
inlining. As a result, when inlining decides to unionsplit
ifelse(x === nothing, 0, x::Union{Nothing, Int}), it looks
back at the definition of x === nothing, realizes it's constrained
by the union split and inserts the appropriate boolean constant.
Next, a new type_tightening_pass goes back and annotates more precise
types for the inlinined select_value and phi nodes. This is sufficient
to get the above code to behave reasonably and should hopefully fix
the performance regression on the various union sum benchmarks
seen in #26795.

@Keno Keno mentioned this pull request May 3, 2018
18 tasks
@ararslan ararslan added the compiler:optimizer Optimization passes (mostly in base/compiler/ssair/) label May 3, 2018
@vtjnash
Copy link
Sponsor Member

vtjnash commented May 7, 2018

As a result, when inlining decides to unionsplit

Why does it do that? The current inliner (correctly) decides that it is more optimal to inline this function without doing union-splitting (partly because of this counter-example case).

Fwiw, ifelse is also fairly likely to become a direct Builtin function for v1.0 (currently it's a generic function because of a deprecation)

Copy link
Sponsor Member

@vtjnash vtjnash left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks probably valid. I dislike doing jump-threading during inlining, but I suppose I'll have to live with it for now.

# Performs minimal backwards inference to catch a couple of interesting, common cases
function minimal_backinf(compact, constraints, unconstrained_types, argexprs)
for i = 2:length(argexprs)
isa(argexprs[i], SSAValue) || continue
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lift argexprs[i] into a local variable will allow the compiler to infer this and generate better (and less) code

end

# Performs minimal backwards inference to catch a couple of interesting, common cases
function minimal_backinf(compact, constraints, unconstrained_types, argexprs)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add type annotations

c = find_constraint(v1, constraints)
if c !== nothing
refined = egal_tfunc(c, compact_exprtype(compact, v2))
if !(ut ⊑ refined)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be better to preserve the inference type, unless we've proven that we have a narrower bound (instead of checking if we're certain we don't have a wider bound):
if refined ⊑ ut

@Keno
Copy link
Member Author

Keno commented May 7, 2018

Nothing in this PR does jump threading.

@vtjnash
Copy link
Sponsor Member

vtjnash commented May 8, 2018

Oh, I didn't notice before how much work this is doing for no benefit. The actual issue here is that the new inliner is doing union-splitting in the wrong order. It should attempt normal inlining first before considering whether other optimizations can apply to split an :invoke site, and then possibly iterate.

Copy link
Sponsor Member

@vtjnash vtjnash left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather fix the actual bug here (wrong order of passes) rather than this

@Keno
Copy link
Member Author

Keno commented May 9, 2018

I can take a look at that, but I don't think it actually fixes the performance problem without this.

@Keno
Copy link
Member Author

Keno commented May 10, 2018

Took a look, we force_noinline any function specialized on union arguments, so there's no difference (because any function with union arguments will never be eligible for inlining). However, the comment in the source says this is due to bugs in type intersection, so I'll see what happens if we remove that restriction.

Keno added 3 commits May 10, 2018 13:20
The benchmarks contain code like this:
```
x::Union{Nothing, Int}
result += ifelse(x === nothing, 0, x)
```
which, perhaps somewhat ironically is quite a bit harder
on the new optimizer than an equivalent code sequence
using ternary operators. The reason for this is that
ifelse gets inferred as `Union{Int, Nothing}`, creating
a phi node of that type, which then causes a union split +
that the optimizer can't really get rid of easily. What this
commit does is add some local improvements to help with the
situation. First, it adds some minimal back inference during
inlining. As a result, when inlining decides to unionsplit
`ifelse(x === nothing, 0, x::Union{Nothing, Int})`, it looks
back at the definition of `x === nothing`, realizes it's constrained
by the union split and inserts the appropriate boolean constant.
Next, a new `type_tightening_pass` goes back and annotates more precise
types for the inlinined `select_value` and phi nodes. This is sufficient
to get the above code to behave reasonably and should hopefully fix
the performance regression on the various union sum benchmarks
seen in #26795.
@StefanKarpinski
Copy link
Sponsor Member

Rerunning CI, if it looks good, let's merge.

@Keno
Copy link
Member Author

Keno commented May 10, 2018

@vtjnash had a couple review comments above which I was planning to address today. I'll start a nanosoldier run with this version on the benchmark PR before that though.

@Keno
Copy link
Member Author

Keno commented May 10, 2018

Discussing with @vtjnash, he really doesn't like this approach and would prefer to take the performance regressions for now and before the 1.0 release merge ifelse/select_value into one and teach inference to be smarter about this. In the meantime I'll rebase the two bug fix commits in here and submit them separately.

Keno added a commit that referenced this pull request May 11, 2018
Keno added a commit that referenced this pull request May 11, 2018
Keno added a commit that referenced this pull request May 11, 2018
This is just the pinode insertion code from #26969, which together
with #27068, should hopefully cover the same benchmarks in a more
acceptable way.
Keno added a commit that referenced this pull request May 12, 2018
@Keno Keno closed this May 12, 2018
@Keno
Copy link
Member Author

Keno commented May 12, 2018

Superseded by the referencing pull requests. Let's keep the branch around for a while for reference though.

@DilumAluthge DilumAluthge deleted the kf/newoptselectvalue branch March 25, 2021 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler:optimizer Optimization passes (mostly in base/compiler/ssair/)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants