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: Better inference of _apply() (splatting) #20343

Merged
merged 3 commits into from
Feb 13, 2017
Merged

Conversation

martinholters
Copy link
Member

@martinholters martinholters commented Jan 31, 2017

Presently, something like

x::Union{Tuple{Foo,Bar},Tuple{Bork,Baz}}
f(x...)

will be inferred as f(::Any...), which is likely suboptimal. This PR tries to improve the situation in two ways:
1 If a Union is splatted, inference tries the individual types. In case multiple Unions are splatted, this would result in many possible combinations; I just limit to an arbitrarily chosen maximum of 7. Otherwise...
2. a Union of several Tuples of the same length is rewritten to a Tuple of Unions, e.g. Tuple{Union{Foo, Bork}, Union{Bar, Baz}} for the example above, if it wasn't handled by 1. already.

As an example, I have asserted the return type of setindex(::Tuple, ...) to be a large Union. This allows

julia> @code_warntype slicedim(ones(1,1), 1, 1)
[...]
  end::Union{Array{Float64,1},Array{Float64,2}}

julia> @code_warntype slicedim(ones(1,1,1,1,1,1), 1, 1)
[...]
  end::Union{Array{Float64,5},Array{Float64,6}}

which are the tightest bounds as slicedim is actually not type-stable. On master, the first one gives Union{Array{Float64,1},Array{Float64,2},Float64}, while for more than 2 dimensions it's just Any. (The modification to setindex on master would just always give Any here).

The code certainly needs some revising, but as I'm completely new to inference, I'd like some feedback on the general idea first. (Blame @vtjnash for encouraging me to even try this!)

Edit: This PR has evolved and grown a bit. It now further enhances cases where

  • a splatted argument of unknown length does not appear last. A call like f(1, ones(n::Int)..., "bar") is now treated like f(::Int, ::Union{Float64,String}...), while on master it is treated like f(::Any...).
  • a general iterable is passed. The iteration protocol is simulated to determine the element type produced by the iterable, instead of just always treating it as Any.

tvnew = Vector{Any}[]
for tv in tp, t in tvs[1]
push!(tvnew, push!(copy(tv), t))
if length(tvnew) > 7
Copy link
Contributor

Choose a reason for hiding this comment

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

should probably make this cutoff a named parameter

Copy link
Member Author

Choose a reason for hiding this comment

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

Definitely. And I have no clue whether 7 is a reasonable choice.

@ararslan ararslan added the compiler:inference Type inference label Jan 31, 2017
@vtjnash
Copy link
Member

vtjnash commented Feb 3, 2017

This looks like a great start. I'm not quite sure if setindex_t is a good idea, but I think you are definitely on the right track.

@martinholters
Copy link
Member Author

This looks like a great start.

Thanks!

I'm not quite sure if setindex_t is a good idea,

Neither am I, but it was the simplest thing to do to test/showcase the improved _apply inference.

but I think you are definitely on the right track.

I'm just refactoring things a bit. Probably it will look something like having a precise_container_type (note the singular) that handles just one of the arguments, and then the loop over the arguments and (if necessary) any Union contents inside abstract_apply, without the need for the extra uniontypes_prod. While doing so, I realized that presently, precise_container_types completely bails out if one the arguments is problematic. E.g. with inference having established x::Int and y::Any, foo(x, y...) would be inferred as foo(::Any...). After the refactoring, it should be easy to instead infer this as foo(:Int, ::Any...). Might there be any reason not to do that?

@martinholters
Copy link
Member Author

Might there be any reason not to do that?

Maybe so. Doing only part of what's possible, all tests still pass locally without significant increase in run time. With one exception, that is:

Test (Worker) | Time (s) | GC (s) | GC % | Alloc (MB) | RSS (MB)
subarray (1)  | 4083.07  | 120.84 |  3.0 | 77743.82   | 1621.79 

Now this does seem a bit excessive. Let me double-check whether I got something wrong...

@martinholters martinholters force-pushed the mh/infer_apply_union branch 4 times, most recently from 0fa6ada to b1e0de8 Compare February 7, 2017 12:28
@martinholters martinholters force-pushed the mh/infer_apply_union branch 2 times, most recently from 958993d to e33f165 Compare February 9, 2017 17:09
@martinholters
Copy link
Member Author

Hm, 32bit travis failed on one the new tests. I wonder whether it's build environment or test/worker association dependent. Build log is backed up here, will restart the travis job.

@martinholters
Copy link
Member Author

Looks like the failure is platform dependent. Time to set up a 32bit VM. Unless someone has a good idea what might be causing this...

If a Union is splatted, inference tries the individual types, unless
this would exceed MAX_APPLY_UNION_ENUM=8 total tries.  As a stopgap in
that case, a Union of several Tuples of the same length is rewritten to
a Tuple of Unions.
@martinholters
Copy link
Member Author

Amazing that it took me so long to realize that the Travis 32bit build does not use a 32bit (virtual) machine but just cross-compiles...

Anyway, the bug that caused the failure should be fixed (a missing initialization). Also, I added the specialization for Array to use eltype instead of abstract_iteration.

I'm still unsure about the last commit, which adds the setindex_t function to add a type-assertion to setindex(::Tuple, ...). While its effect for slicedim is quite nice, I wonder whether it's worth it. Should I just remove that commit? Or try whether setindex itself can be rewritten to be more inference-friendly without resorting to this monstrous type-assert? Thoughts?

@martinholters martinholters changed the title WIP/RFC: Better inference of _apply(..., Union{Tuple...}, ...) WIP/RFC: Better inference of _apply() (splatting) Feb 10, 2017
end
elseif tti <: AbstractArray && i == n
result[i] = Any[Vararg{eltype(tti)}]
# `type` is the inferred type for expression `arg`.
Copy link
Member

Choose a reason for hiding this comment

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

typ instead of type?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup, will fix.

@JeffBezanson
Copy link
Member

This is some great work!

The huge union in setindex_t also makes me a bit nervous. Maybe better to leave it out for now, since it's not needed for the main improvement here.

Seems like this fixes #19957? Could you add a test for that?

@martinholters
Copy link
Member Author

martinholters commented Feb 13, 2017

Seems like this fixes #19957?

Not completely: it will infer f(1...) like f(::Int...) instead of f(::Any...), but not like f(::Int). Certainly an improvement, but does that count as fixing #19957?

Could you add a test for that?

To the extent it is actually fixed, yes, will do.

Loop over the arguments in `abstract_apply` and invoke the
modified/renamed `precise_container_type` for each individual argument.
Translate some cases where the container type cannot be inferred
precisely into Vararg's (instead of bailing out and inferring
`f(::Any...)`) and fuse Vararg's with further arguments into a single
trailing Vararg.
Closes #20518 and improves inference when splatting genetal iterables.
@martinholters martinholters changed the title WIP/RFC: Better inference of _apply() (splatting) RFC: Better inference of _apply() (splatting) Feb 13, 2017
@martinholters
Copy link
Member Author

Let's see whether this has any effect on the benchmarks:
@nanosoldier runbenchmarks(ALL, vs = ":master")

@nanosoldier
Copy link
Collaborator

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels

@martinholters
Copy link
Member Author

Two of the improvements look real, everything else is noise?

@tkelman
Copy link
Contributor

tkelman commented Feb 13, 2017

the subarray slowdowns look pretty consistent though

@KristofferC
Copy link
Member

Easiest to run again:

@nanosoldier runbenchmarks(ALL, vs = ":master")

@martinholters
Copy link
Member Author

martinholters commented Feb 13, 2017

It looks quite consistent indeed, as the same operations seem to be benchmarked twice: JuliaCI/BaseBenchmarks.jl@da9fda3#commitcomment-20858355

@martinholters
Copy link
Member Author

Oh boy, @code_warntype for the benchmarked perf_lucompletepivCopy! gives a 1000+ lines output. But no type-instabilities there. In fact, no _apply there. So at first glance, no idea where the slowdown might come.

@nanosoldier
Copy link
Collaborator

Your benchmark job has completed - no performance regressions were detected. A full report can be found here. cc @jrevels

@tkelman
Copy link
Contributor

tkelman commented Feb 13, 2017

fair enough then

@JeffBezanson JeffBezanson merged commit 3f14659 into master Feb 13, 2017
@StefanKarpinski StefanKarpinski deleted the mh/infer_apply_union branch February 13, 2017 21:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler:inference Type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants