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

Fix return type in broadcast when there are Type arguments #19421

Merged
merged 1 commit into from
Dec 13, 2016

Conversation

pabloferz
Copy link
Contributor

@pabloferz pabloferz commented Nov 25, 2016

Fixes #19419

@KristofferC
Copy link
Member

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

@Sacha0
Copy link
Member

Sacha0 commented Nov 25, 2016

Am I making a mistake below? ziptype now returns the correct type, but inference seems unhappy with the _default_eltype-first-generator-zip mechanism:

julia> begin
           ftype(f, A) = typeof(f)
           ftype(f, A...) = typeof(a -> f(a...))
           ftype(T::Type, A) = Type{T}
           ftype(T::Type, A...) = Type{T}
           ziptype(A) = Tuple{eltype(A)}
           ziptype(T::Type) = Tuple{Type{T}}
           ziptype(A, B) = Iterators.Zip2{ziptype(A), ziptype(B)}
           @inline ziptype(A, B, C, D...) = Iterators.Zip{ziptype(A), ziptype(B, C, D...)}
       end
ziptype (generic function with 4 methods)

julia> As = (Int, [1,2,3])
(Int64,[1,2,3])

julia> zt = ziptype(As...)
Base.Iterators.Zip2{Tuple{Type{Int64}},Tuple{Int64}}

julia> ft = ftype(round, As...)
##1#2{Base.#round}

julia> Base._default_eltype(Base.Generator{zt,ft})
Any

yet

julia> Core.Inference.return_type(round, Tuple{Type{Int},Int})
Int64

(I imagine there is a good reason for the _default_eltype-first-generator-zip mechanism? I.e. rather than simply constructing a call to return_type like that above?) Best!

@nanosoldier
Copy link
Collaborator

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

@pabloferz
Copy link
Contributor Author

pabloferz commented Nov 27, 2016

@Sacha0 I don't understand all the underlying details in inference all too well, but I'll try to convey what I can grasp from the problems that led to the current mechanism, which has the limitation you noticed.

Core.Inference.return_type is inherently non-inferable, but it gets a special treatment during inference, I believe, when called inside a pure function, so that rules out using it inside broadcast_c directly. So you might wonder if you can write a pure function, say foo, that calls Core.Inference.return_type, takes a function and its argument types, and which you can embed in broadcast. I think a difficulty with this, is that if you don't know the function you'll be calling, you can't ensure the pureness of foo. Additionally, you can't mark foo as pure given the way inference works.

Maybe there is another approach that could work, but I haven't been able to devise one. The only thing that I have found to work most of the time is the _default_eltype-first-generator-zip mechanism.

@vtjnash
Copy link
Member

vtjnash commented Nov 28, 2016

Core.Inference.return_type is inherently non-inferable, but it gets a special treatment during inference, I believe, when called inside a pure functio

That's backwards. It's distinctly not pure, so it must never be called from a function annotated as such. But since the bound on its return value is actually inferrable, it has special handling inside type inference. (I still need to look at this PR to understand if it's correct, but I've been afk for Thanksgiving)

@pabloferz
Copy link
Contributor Author

pabloferz commented Nov 28, 2016

@vtjnash Thanks for clarifying. That makes more sense.

@pabloferz
Copy link
Contributor Author

pabloferz commented Nov 28, 2016

Just a question then (to understand the situation better). Under what conditions the bound of return_type cannot be inferred or when it fails to get handled during inference? (given it is not called from a function annotated as pure)

@kshyatt kshyatt added the types and dispatch Types, subtyping and method dispatch label Dec 1, 2016
ftype(T::DataType, A) = Type{T}
ftype(T::DataType, A...) = Type{T}
ftype(T::Type, A) = Type{T}
ftype(T::Type, A...) = Type{T}
Copy link
Member

Choose a reason for hiding this comment

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

IIUC, the eltype of the type ziptype constructs is a tuple, which necessitates ftype(f, A...) = typeof(a -> f(a...)) rather than ftype(f, A...) = f. Is that correct? If so, why does ftype(T::Type, A...) = Type{T} suffice rather than something like ftype(T::Type, A...) = typeof(a -> T(a...))? Thanks!

@Sacha0
Copy link
Member

Sacha0 commented Dec 12, 2016

It's distinctly not pure, so it must never be called from a function annotated as such.

@vtjnash: promote_eltype_op is annotated pure, and calls promote_op. promote_op calls _default_eltype, which in turn calls Core.Inference.return_type. So IIUC, promote_eltype_op is invalid? Thanks!

@vtjnash
Copy link
Member

vtjnash commented Dec 12, 2016

Absolutely.

@pabloferz
Copy link
Contributor Author

The use of @pure in promote_eltype_op is a reminiscent of the time when promote_op wasn't relying on inference. I removed it and simplified the whole mechanism so it works better.

@pabloferz
Copy link
Contributor Author

Could we ask @nanosoldier again how is the performance?

@inline ziptype(A, B, C, D...) = Iterators.Zip{Tuple{eltype(A)}, ziptype(B, C, D...)}
@pure typestuple(a) = Tuple{eltype(a)}
@pure typestuple(T::Type) = Tuple{Type{T}}
@pure typestuple(a, b...) = Tuple{typestuple(a).types..., typestuple(b...).types...}
Copy link
Member

Choose a reason for hiding this comment

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

💯

@Sacha0
Copy link
Member

Sacha0 commented Dec 12, 2016

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

@Sacha0
Copy link
Member

Sacha0 commented Dec 12, 2016

The most recent simplifications look great! Do they make the test result now inferable? Best!

@pabloferz
Copy link
Contributor Author

The most recent simplifications look great! Do they make the test result now inferable?

Yep

@nanosoldier
Copy link
Collaborator

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

@@ -352,3 +352,6 @@ StrangeType18623(x,y) = (x,y)
# Issue 18622
@test @inferred muladd.([1.0], [2.0], [3.0])::Vector{Float64} == [5.0]
@test @inferred tuple.(1:3, 4:6, 7:9)::Vector{Tuple{Int,Int,Int}} == [(1,4,7), (2,5,8), (3,6,9)]

# 19419
@test @inferred broadcast(round, Int, [1])::Vector{Int} == [1]
Copy link
Member

Choose a reason for hiding this comment

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

With broadcast(round, Int, [1]) now inferred, drop the ::Vector{Int}? Also, shouldn't these combined @test-@inferred tests be written @test @inferred(broadcast(round, Int, [1])) == [1] instead? Otherwise the @inferred checks whether the == expression is inferable rather than the LHS? Likewise several of the preceding tests. Best!

Copy link
Member

@Sacha0 Sacha0 left a comment

Choose a reason for hiding this comment

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

These changes look great. Travis, Appveyor, and Nanosoldier approve. Absent objections or requests for time, I plan to merge this following the test touchups. Best!

@Sacha0
Copy link
Member

Sacha0 commented Dec 12, 2016

(As an aside, though this PR dramatically reduces the amount of red in @code_warntype broadcast(round, Int, [1, 2, 3]), some red remains. Perhaps worth looking into at a later point? Best!)

@pabloferz
Copy link
Contributor Author

Tests fixed. The AV error was an unrelated server failure. Some of the map_newindexer methods in Broadcast would have to be changed to reduce those red marks, but that can be done later as you point out (those do not affect the overall inferability of broadcast).

@Sacha0 Sacha0 merged commit 2ee7311 into JuliaLang:master Dec 13, 2016
@Sacha0
Copy link
Member

Sacha0 commented Dec 13, 2016

Thanks again @pabloferz! 💯

@pabloferz pabloferz deleted the pz/ziptype branch December 13, 2016 05:49
@KristofferC
Copy link
Member

This (probably) caused the following regression:

julia> using StaticArrays

julia> s = rand(SVector{3});

julia> Base.Test.@inferred s * 0.0

Ping @andyferris

@KristofferC
Copy link
Member

Not saying that there is anything wrong with this PR, it could be how StaticArray uses broadcast. Just bringing it up.

@pabloferz
Copy link
Contributor Author

pabloferz commented Dec 13, 2016

The problem with StaticArrays comes from the fact that the new approach now suffers (I believe) a manifestation of #15276. The old approach didn't had this problem but it is unable get things like broadcast(round, Int, [1]) to be inferable.

I could implement a mixed approach between the one in this PR and the previous mechanism that handles (except for some possible corner cases) both issues.

Another option is see if something can be done on the inference end @JeffBezanson?

Here is an example of something that used to work with the old approach but currently does not

foo(f, A, n) = broadcast(x -> f(x, n), A)
foo(+, [1.0, 2.0, 3.0], 1) # Not inferable but it was before

@Sacha0
Copy link
Member

Sacha0 commented Dec 13, 2016

Ouch. Just confirmed

foo(f, A, n) = broadcast(x -> f(x, n), A)
Base.Test.@inferred foo(+, [1.0, 2.0, 3.0], 1)

previously worked (with concrete inferred type), but now fails. Odd though, the _default_eltype-first-generator-zip mechanism appeared to likewise fail in my testing for this related discourse thread? Testing further. Best!

@Sacha0
Copy link
Member

Sacha0 commented Dec 13, 2016

Checked, both the example above and that from the linked discourse thread fail on master, but both work on the commit prior to the merge commit for this pull request. I must've made a mistake when testing for the related discourse thread? Best!

@tkelman
Copy link
Contributor

tkelman commented Dec 13, 2016

If there isn't an easy fix to be had soon, could at least add some test_broken cases that we would ideally like to work, for tracking purposes

@Sacha0
Copy link
Member

Sacha0 commented Dec 13, 2016

the new approach now suffers (I believe) a manifestation of #15276. The old approach didn't had this problem but it is unable get things like broadcast(round, Int, [1]) to be inferable.

Ref. @yuyichao's comment on discourse.

Sacha0 added a commit to Sacha0/julia that referenced this pull request Dec 25, 2016
…re cases.

Re-simplify broadcast's eltype promotion mechanism as in JuliaLang#19421. With benefit of JuliaLang#19667, this simplified mechanism should handle additional cases (e.g. closures accepting more than two arguments). Also rename the mechanism more precisely (_broadcast_type -> _broadcast_eltype).
Sacha0 added a commit to Sacha0/julia that referenced this pull request Dec 30, 2016
…re cases.

Re-simplify broadcast's eltype promotion mechanism as in JuliaLang#19421. With benefit of JuliaLang#19667, this simplified mechanism should handle additional cases (e.g. closures accepting more than two arguments). Also rename the mechanism more precisely (_broadcast_type -> _broadcast_eltype).
Sacha0 added a commit to Sacha0/julia that referenced this pull request Dec 30, 2016
…re cases.

Re-simplify broadcast's eltype promotion mechanism as in JuliaLang#19421. With benefit of JuliaLang#19667, this simplified mechanism should handle additional cases (e.g. closures accepting more than two arguments). Also rename the mechanism more precisely (_broadcast_type -> _broadcast_eltype).
Sacha0 added a commit to Sacha0/julia that referenced this pull request Dec 31, 2016
…re cases.

Re-simplify broadcast's eltype promotion mechanism as in JuliaLang#19421. With benefit of JuliaLang#19667, this simplified mechanism should handle additional cases (e.g. closures accepting more than two arguments). Also rename the mechanism more precisely (_broadcast_type -> _broadcast_eltype).
Sacha0 added a commit to Sacha0/julia that referenced this pull request Dec 31, 2016
…re cases.

Re-simplify broadcast's eltype promotion mechanism as in JuliaLang#19421. With benefit of JuliaLang#19667, this simplified mechanism should handle additional cases (e.g. closures accepting more than two arguments). Also rename the mechanism more precisely (_broadcast_type -> _broadcast_eltype).
Sacha0 added a commit to Sacha0/julia that referenced this pull request Dec 31, 2016
…re cases.

Re-simplify broadcast's eltype promotion mechanism as in JuliaLang#19421. With benefit of JuliaLang#19667, this simplified mechanism should handle additional cases (e.g. closures accepting more than two arguments). Also rename the mechanism more precisely (_broadcast_type -> _broadcast_eltype).
Sacha0 added a commit to Sacha0/julia that referenced this pull request Jan 1, 2017
…re cases.

Re-simplify broadcast's eltype promotion mechanism as in JuliaLang#19421. With benefit of JuliaLang#19667, this simplified mechanism should handle additional cases (e.g. closures accepting more than two arguments). Also rename the mechanism more precisely (_broadcast_type -> _broadcast_eltype, typestuple -> eltypestuple).
Sacha0 added a commit to Sacha0/julia that referenced this pull request Jan 1, 2017
…re cases.

Re-simplify broadcast's eltype promotion mechanism as in JuliaLang#19421. With benefit of JuliaLang#19667, this simplified mechanism should handle additional cases (e.g. closures accepting more than two arguments). Also rename the mechanism more precisely (_broadcast_type -> _broadcast_eltype, typestuple -> eltypestuple).
tkelman pushed a commit that referenced this pull request Jan 1, 2017
…re cases. (#19723)

Re-simplify broadcast's eltype promotion mechanism as in #19421. With benefit of #19667, this simplified mechanism should handle additional cases (e.g. closures accepting more than two arguments). Also rename the mechanism more precisely (_broadcast_type -> _broadcast_eltype, typestuple -> eltypestuple).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
types and dispatch Types, subtyping and method dispatch
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants