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

Use new broadcast API #348

Closed
wants to merge 17 commits into from
Closed

Use new broadcast API #348

wants to merge 17 commits into from

Conversation

pablosanjose
Copy link
Contributor

@pablosanjose pablosanjose commented Dec 7, 2017

Attempt to fix #347

(Please review carefully, as I'm still quite new at this.)

I implemented a StaticArrayStyle{N} <: AbtractArrayStyle{N} to plug into the new broadcast API introduced in julialang/#23939, together with two specialised indirections for broadcast and broadcast! that call the old @generated _broadcast and @generated _broadcast! routines. The latter does still seem to be a bit faster and allocate a little less than the Base broadcast! fallback. However, the Base fallback also works.

I see tight @code_native output, similar or identical to the one before julialang/#23939. Timings and allocations also seem identical, although I didn't do exhaustive testing. [EDIT: not always] [EDIT2: fixed now, hopefully]

Caveat
The package tests now work again, except for those involving broadcast between Scalar(...) and normal Arrays. I'm afraid I need a bit of help with this one. As an example Scalar(3) .* [1,2,3] fails with the same error as Scalar(3) * 2, a BoundsError. This is the case both before and after julialang/#23939, but for some reason Scalar(3) .* [1,2,3] did work with the old broadcast API, and returned Array{Int}[2, 4, 6]. What is the expected behavior for this? What should Scalar(3) * 2 give? How should Scalar(3) .* [1,2,3] be fixed with the new broadcast API?

[EDIT: fixed Scalar broadcast. I needed to properly define the correct broadcast promotions using binary methods for BroadcastStyle]

@pablosanjose
Copy link
Contributor Author

pablosanjose commented Dec 7, 2017

This still needs some work. The @code_native output is not as good as before. I'm not completely sure whether this is overhead from the new broadcast machinery or a faulty implementation on my part...

julia> f() = SVector(0,2,3) .* SVector(1,2,3)
f (generic function with 1 method)

Before:

julia> @btime f()
  1.345 ns (0 allocations: 0 bytes)
3-element SVector{3,Int64}:
 0
 4
 9

After:

julia> @btime f()
  3.712 ns (0 allocations: 0 bytes)
3-element SVector{3,Int64}:
 0
 4
 9

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.07%) to 93.297% when pulling 1a00c3f on pablosanjose:newbroadcast into bb51021 on JuliaArrays:master.

@codecov-io
Copy link

codecov-io commented Dec 7, 2017

Codecov Report

Merging #348 into master will decrease coverage by 0.06%.
The diff coverage is 80%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #348      +/-   ##
==========================================
- Coverage   93.36%   93.29%   -0.07%     
==========================================
  Files          37       37              
  Lines        2713     2715       +2     
==========================================
  Hits         2533     2533              
- Misses        180      182       +2
Impacted Files Coverage Δ
src/broadcast.jl 97.72% <80%> (-2.28%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bb51021...5d5bab6. Read the comment docs.

@pablosanjose
Copy link
Contributor Author

Aha. The problem was that specialising broadcast(f, ::StaticArrayStyle, ::Type{ElType}, inds::Tuple, As...) was unnecessarily slow, because both ElType and inds where computed with the generic combine_eltypes and combine_indices from Base. Those where not used, as the old method relied on the static StaticArray.broadcast_sizes methods. Specialising on broadcast(f, ::StaticArrayStyle, ::Void, ::Void, As...) instead gets us back to the old timings:

julia> @btime f()
  1.382 ns (0 allocations: 0 bytes)
3-element SVector{3,Int64}:
 0
 4
 9

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.07%) to 93.297% when pulling 1a00c3f on pablosanjose:newbroadcast into bb51021 on JuliaArrays:master.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.07%) to 93.297% when pulling 1a00c3f on pablosanjose:newbroadcast into bb51021 on JuliaArrays:master.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.07%) to 93.297% when pulling 75dc71e on pablosanjose:newbroadcast into bb51021 on JuliaArrays:master.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.07%) to 93.297% when pulling 2edd21e on pablosanjose:newbroadcast into bb51021 on JuliaArrays:master.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.07%) to 93.297% when pulling b146f8a on pablosanjose:newbroadcast into bb51021 on JuliaArrays:master.

src/broadcast.jl Outdated
BroadcastStyle(::Type{<:StaticArray{D, T, N}}) where {D, T, N} = StaticArrayStyle{N}()

# Fix Precedence: Make StaticArray - Array -> Array
BroadcastStyle(::StaticArrayStyle{M}, ::Broadcast.VectorStyle) where {N,M} = Broadcast.Unknown()
Copy link
Member

Choose a reason for hiding this comment

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

N is undefined.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks Andy!

src/broadcast.jl Outdated
BroadcastStyle(::StaticArrayStyle{M}, ::Broadcast.VectorStyle) where {N,M} = Broadcast.Unknown()
BroadcastStyle(::StaticArrayStyle{M}, ::Broadcast.MatrixStyle) where {N,M} = Broadcast.Unknown()

# Add a broadcast method that calls the old @generated routine
Copy link
Member

Choose a reason for hiding this comment

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

"old" isn't really necessary here

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.07%) to 93.297% when pulling 5d5bab6 on pablosanjose:newbroadcast into bb51021 on JuliaArrays:master.

Copy link
Member

@timholy timholy left a comment

Choose a reason for hiding this comment

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

Looks quite good, thanks for tackling this!

src/broadcast.jl Outdated

# Fix Precedence: Make StaticArray - Array -> Array
BroadcastStyle(::StaticArrayStyle{M}, ::Broadcast.VectorStyle) where M = Broadcast.Unknown()
BroadcastStyle(::StaticArrayStyle{M}, ::Broadcast.MatrixStyle) where M = Broadcast.Unknown()
Copy link
Member

Choose a reason for hiding this comment

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

VectorStyle and MatrixStyle are unfortunate (and hopefully temporary) objects because we don't trust that sparse broadcasting generalizes correctly beyond Array (see JuliaLang/julia#23939 (comment)). However, this suggests we'll have to support them throughout julia 1.x. 😢

Long-term the one we want to implement would be with DefaultArrayStyle. Instead of returning Unknown, it might be best to do something like this (untested):

BroadcastStyle(::StaticArrayStyle{M}, ::Broadcast.DefaultArrayStyle{N}) where {M,N} =
    DefaultArrayStyle(Broadcast._max(Val(M), Val(N))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey Tim, thanks for reviewing! Your designs are a pleasure to work with.

So you suggest including this rule, in addition to the VectorStyle and MatrixStyle ones, to take over once the latter are removed, yes? Done.

Copy link
Member

Choose a reason for hiding this comment

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

Right. Also the DefaultArrayStyle is necessary even now for dimensions higher than 2.

Thanks for the kind words about the API. I think it's also fair to say that unfortunately they don't help StaticArrays that much; they're more oriented around improving the API for mutable arrays. But at least it's documented, and hopefully that's worth something.

Really grateful to you for tackling this!

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.07%) to 93.297% when pulling b70fae3 on pablosanjose:newbroadcast into bb51021 on JuliaArrays:master.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.07%) to 93.297% when pulling d7b23dd on pablosanjose:newbroadcast into bb51021 on JuliaArrays:master.

@pablosanjose
Copy link
Contributor Author

Please forgive the supernoob question: what is that failed check from coveralls? Is it just because I need to add some tests? (I'm not sure if this is waiting for me to do something...)

@tkoolen
Copy link
Contributor

tkoolen commented Jan 10, 2018

Note that there have been additional changes to broadcast! recently (see JuliaLang/julia#24992), which should enable resolution of your TODO. See https://docs.julialang.org/en/latest/manual/interfaces/#extending-in-place-broadcast-1 for the required method signatures.

Re: coveralls: a slight decrease in code coverage is to be expected. Code coverage is determined on Julia 0.6, so the 0.7 code doesn't get run. It doesn't matter (check is not required to pass before merging).

@tkoolen
Copy link
Contributor

tkoolen commented Jan 10, 2018

Actually, here's a PR against your branch that implements the change: https://github.com/pablosanjose/StaticArrays.jl/pull/1.

@timholy
Copy link
Member

timholy commented Jan 10, 2018

And probably more changes coming...JuliaLang/julia#25377

Adapt to new broadcast! implementation in Base.
@coveralls
Copy link

Coverage Status

Coverage decreased (-0.07%) to 93.297% when pulling 0ccfc43 on pablosanjose:newbroadcast into bb51021 on JuliaArrays:master.

@andyferris
Copy link
Member

Sorry, I haven't got around to this in a while. Is this good to go?

@pablosanjose
Copy link
Contributor Author

It is as far as I'm concerned... (At least until JuliaLang/julia#25377 rains hellfire on us! :-P)

@pablosanjose
Copy link
Contributor Author

I just tried this again in the latest Julia master, and I think there are still broadcast problems.

julia> @SVector[1,2,3]*2
ERROR: UndefVarError: Inference not defined
Stacktrace:
 [1] @generated body at /Users/pablo/.julia/v0.7/StaticArrays/src/broadcast.jl:152 [inlined]
 [2] _broadcast at /Users/pablo/.julia/v0.7/StaticArrays/src/broadcast.jl:94 [inlined]
 [3] broadcast at /Users/pablo/.julia/v0.7/StaticArrays/src/broadcast.jl:62 [inlined]
 [4] broadcast at ./broadcast.jl:615 [inlined]
 [5] *(::SVector{3,Int64}, ::Int64) at /Users/pablo/.julia/v0.7/StaticArrays/src/linalg.jl:26
 [6] top-level scope

Not sure if this happened some weeks ago. I'll try to look into it if I can find the time.

@andyferris
Copy link
Member

FYI @pablosanjose I've been working on a branch which deals with all the v0.7 carnage and have incorporated these changes there (thanks!).

However every day I nearly get the tests passing and then the next day something else breaks. (This time it's Base.LinAlg -> LinearAlgebra stdlib change. Fun.)

@pablosanjose
Copy link
Contributor Author

You have all my support and empathy. StaticArrays is a central tool for many of us. Keep at it!

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.

ERROR: MethodError: no method matching result_style(::Type{StaticArray})
6 participants