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

RFC: Change lowering of destructuring to avoid const prop dependence #37268

Closed
wants to merge 2 commits into from

Conversation

Keno
Copy link
Member

@Keno Keno commented Aug 28, 2020

I'm currently doing some work with inference passes that have const
prop (temporarily) disabled and I noticed we actually rely on it
quite a bit for basic things. That's not terrible - const prop works
pretty well after all, but it still imposes a cost and while I want
to support it in my AD use case also, it makes destructuring quite
expensive, because everything needs to be inferred twice. This PR
is an experiment in changing the lowering to avoid having to const
prop the index. Rather than lowering (a,b,c) = foo() as:

it = foo()
a, s = indexed_iterate(it, 1)
b, s = indexed_iterate(it, 2, s)
c, s = indexed_iterate(it, 3, s)

we lower as:

it = foo()
iterate, index = index_and_itereate(it)
x = iterate(it)
a = index(x, 1)
y = iterate(it, x)
b = index(y, 2)
z = iterate(it, z)
c = index(z, 3)

For tuples iterate would simply return the first argument and
index would be getfield. That way, there is no const prop,
since getfield is called directly and inference can directly
use its tfunc. For the fallback case iterate is basically
just Base.iterate, with just a slight tweak to give an intelligent
error for short iterables.

On simple functions, there isn't much of a difference in execution
time, but benchmarking something more complicated like:

function g()
  a, = getfield(((1,),(2.0,3),("x",),(:x,)), Base.inferencebarrier(1))
  nothing
end

shows about a 20% improvement in end-to-end inference/optimize time,
which is substantial.

@@ -66,7 +66,7 @@ const TAGS = Any[
:(=), :(==), :(===), :gotoifnot, :A, :B, :C, :M, :N, :T, :S, :X, :Y, :a, :b, :c, :d, :e, :f,
:g, :h, :i, :j, :k, :l, :m, :n, :o, :p, :q, :r, :s, :t, :u, :v, :w, :x, :y, :z, :add_int,
:sub_int, :mul_int, :add_float, :sub_float, :new, :mul_float, :bitcast, :start, :done, :next,
:indexed_iterate, :getfield, :meta, :eq_int, :slt_int, :sle_int, :ne_int, :push_loc, :pop_loc,
:index_and_iterate, :getfield, :meta, :eq_int, :slt_int, :sle_int, :ne_int, :push_loc, :pop_loc,
Copy link
Member

Choose a reason for hiding this comment

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

To be super conservative, we should leave this here and replace a reserved slot with the new symbol instead. Of course, loading code with indexed_iterate won't work anyway, but at least it's slightly more compatible.

@JeffBezanson
Copy link
Member

👍 to the concept. Shouldn't it be called iterate_and_index? 😄

base/missing.jl Outdated
@@ -69,6 +69,7 @@ convert(::Type{T}, x::T) where {T>:Union{Missing, Nothing}} = x
convert(::Type{T}, x) where {T>:Missing} = convert(nonmissingtype_checked(T), x)
convert(::Type{T}, x) where {T>:Union{Missing, Nothing}} = convert(nonmissingtype_checked(nonnothingtype_checked(T)), x)

index_and_iterate(::Missing) = throw(MethodError(iterate, (missing,)))
Copy link
Member

Choose a reason for hiding this comment

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

I assume this is there for the same reason as the one for nothing, but would it make sense to add a comment here as well?

@Keno Keno force-pushed the kf/destructlower branch from 65b5880 to d28d0fb Compare August 30, 2020 18:51
I'm currently doing some work with inference passes that have const
prop (temporarily) disabled and I noticed we actually rely on it
quite a bit for basic things. That's not terrible - const prop works
pretty well after all, but it still imposes a cost and while I want
to support it in my AD use case also, it makes destructuring quite
expensive, because everything needs to be inferred twice. This PR
is an experiment in changing the lowering to avoid having to const
prop the index. Rather than lowering `(a,b,c) = foo()` as:

```
it = foo()
a, s = indexed_iterate(it, 1)
b, s = indexed_iterate(it, 2)
c, s = indexed_iterate(it, 3)
```

we lower as:

```
it = foo()
iterate, index = iterate_and_index(it)
x = iterate(it)
a = index(x, 1)
y = iterate(it, y)
b = index(y, 2)
z = iterate(it, z)
c = index(z, 3)
```

For tuples `iterate` would simply return the first argument and
`index` would be `getfield`. That way, there is no const prop,
since `getfield` is called directly and inference can directly
use its tfunc. For the fallback case `iterate` is basically
just `Base.iterate`, with just a slight tweak to give an intelligent
error for short iterables.

On simple functions, there isn't much of a difference in execution
time, but benchmarking something more complicated like:
```
function g()
  a, = getfield(((1,),(2.0,3),("x",),(:x,)), Base.inferencebarrier(1))
  nothing
end
```

shows about a 20% improvement in end-to-end inference/optimize time,
which is substantial.
@Keno Keno force-pushed the kf/destructlower branch from d28d0fb to 8b74afb Compare August 30, 2020 19:04
Keno added a commit that referenced this pull request Aug 30, 2020
So after thinking about #37268, I was wondering what would happen
if one applied the same trick to the lowering of `getproperty`
as well. This PR starts at that, but is mostly a placeholder for
discussion of whether this is something we want to do. In particular,
this PR lowers `a.b` to:

```
getproperty(a)(a, :b)
```

with a default implementation of

```
getproperty(a) = getfield
```

The inference benefit is as expected. Timing end-to-end inference&optimize
time of:

```
struct Foo; end
f(a::Foo) = a.x
```

shows that time to infer `f` drops by about a third (because it doesn't need to
infer getproperty twice). Overall build time of the system image drops
by 6% for me.

If we do decide to go this way, things that would still need to be fixed:

- [] Do the same for `setproperty!`
- [] `ccall` doesn't like this lowering. Didn't look into it too closely
- [] Needs a fallback to backwards compatibility. I'm thinking something
along the lines of

```
getproperty(x) = hasmethod(getproperty, Tuple{typeof(x), Symbol}) ? getproperty : getfield
```

but obviously it would need inference integration to be fast.
base/namedtuple.jl Outdated Show resolved Hide resolved
Co-authored-by: Ciro Cavani <ciro.cavani@gmail.com>
@simeonschaub
Copy link
Member

Bump. Is this good to go?

@simeonschaub
Copy link
Member

Just bumping this again. 🙂

@Keno
Copy link
Member Author

Keno commented Dec 31, 2020

We decided we may want to get rid of special lowering for destructuring, so we decided against this.

@Keno Keno closed this Dec 31, 2020
@DilumAluthge DilumAluthge deleted the kf/destructlower branch March 25, 2021 21:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants