Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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
Faster mapreduce for Broadcasted #31020
Faster mapreduce for Broadcasted #31020
Changes from 2 commits
f4c68ee
85f6405
cc7c05a
1a60367
ae55dc2
ece81cf
ee68a45
758e003
cec63e7
fb2ff9e
cf04cc8
cedeec6
File filter
Filter by extension
Conversations
Jump to
There are no files selected for viewing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turned out my previous approach (combine
IndexStyle
of each argument) was wrong since it didn't work with, e.g.,broadcasted(+, zeros(5), zeros(1, 4))
. Each argument can beIndexLinear
even though the broadcasted indexing is not (that's the main job of broadcasted).So now the new approach is to return an equivalent of
IndexStyle(bc.axes[1])
iflength(bc.axes) == 1
and then returnIndexCartesian()
otherwise. As I needAxes
type parameter now, this implementation requires theBroadcasted
to beinstantiate
d first. I guess this is OK since that's the usual broadcasting pipeline.Is this implementation fine? Is
IndexStyle(bc.axes[1]) == IndexLinear() && length(bc.axes) == 1
the only possibleIndexLinear()
case that is detectable in a type-stable manner? That is to say, I'm ignoring the case likebroadcasted(+, zeros(1, 4), zeros(1, 4))
as I need to look at the value to check that it supports linear indexing.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't really require the
Broadcasted
to beinstantiate
d to get anIndexStyle
— it just means that if it's not instantiated that it falls back to a cartesian implementation. AllBroadcasted
s should support cartesian indexing; only one-dimensional ones will allow straight integers.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's much more accurate way to put it. I wanted to say that "for the broadcasted to be dispatched to the optimized method", it has to be
instantiate
d first.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to look into how indexing is defined for
Broadcasted
(especially for how it interacts withAxes
) and if thisIndexStyle
is correct.However, it seems to me that it'd be much simpler if we define
IndexStyle(::Broadcasted)
thanIndexStyle(::Type{<:Broadcasted})
as it would let us useeachindex(::Broadcasted)
inIndexStyle
. Does it makes sense to defineIndexStyle(::Broadcasted)
directly than the type trait?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK
IndexStyle
needs to take a type rather than an instance so that you can know statically what kind of indices to expect (that's more or less the definition of a trait). At least that's how its documented.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My comment is based on the implementation detail in reduce.jl that
IndexStyle
is called on instancejulia/base/reduce.jl
Line 299 in 236c69b
which is valid since
IndexStyle
is callable onAbstractArray
:julia/base/indices.jl
Line 70 in 236c69b
However, to make
IndexStyle
a proper trait forBroadcasted
, I defined methods for types and added the method for instance on top of them, just like how it's done forAbstractArray
:julia/base/broadcast.jl
Line 227 in f4c68ee
Note that direct definition of
IndexStyle(::Broadcasted)
would be type-stable ifeachindex
is so. Ifeachindex
is not type-stable we don't have the performance gain anyway. Initially, I wasn't aiming for adding public interface forBroadcasted
in this PR so I thought relying on the implementation detail thatIndexStyle
is only called on instance was OK. However, I also understand that contaminating "signature space" ofIndexStyle
is not particularly a clean solution.If we say that
IndexStyle
forBroadcasted
has to be a proper trait, we need to note thatIndexStyle
must be properly defined wheneveraxes
is customized sinceaxes
is a public interface.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but as long as you define
IndexStyle
it should be a proper trait, independent from how it's used internally. Otherwise, you'd better define a custom internal function.Anyway, what's the problem with the current implementation in this PR? Just that's it's complicated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. So, reading the documentation and the code more, it looks like
Base.axes(::Broadcasted{MyStyle})
is not an overloadable method? From the documentation,Base.axes(x)
is overloadable ifx
participate in the broadcasting. This method is then called viacombine_axes
inaxes(::Broadcasted)
. I was initially worried thatIndexStyle(::Type{<:Broadcasted})
andBase.axes(::Broadcasted)
can become incompatible if downstream package authors are not careful but it looks like I don't need to worry about it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I needed to define this
AbstractBroadcasted
as broadcast.jl is loaded later than reduce.jl. ATM it's just a hack to make this work, but I wonder if it makes sense to defineor even
so that it's much easier for downstream projects to use indexing-based
mapreduce
implementation.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having an abstract type or a trait like
Indexable
would also be useful to supportmapreduce
over dimensions withskipmissing
(#28027). It would avoid duplicating some methods.Though a trait would probably be better than an abstract type, since it would be more flexible. In particular, an object returned by
skipmissing
could be marked asIndexable
or not depending on whether it wraps anIndexable
object (like an array) or another iterable. It would also allow objects that already inherit from a non-indexable abstract type to implementIndexable
if they want, which wouldn't be possible with an abstract type.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we go with trait direction, one option is to use
IndexStyle
. Something like this:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a clever idea. I think the union here is sensible for now — that trait could possibly come later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Differing the whole designing work for the trait makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vtjnash mentioned there may be another way around this bootstrapping issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that the 1.4 feature freeze is approaching, I support merging the PR with the
Union
for now, and try to use a more general trait later.FWIW, this PR is giving us incredibly good performance to be able to reuse the pairwise summation algorithm with weights at JuliaStats/StatsBase.jl#518. Great work!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for testing this PR in a real-world scenario!