-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
broadcast!(f, C) for sparse vector/matrix C #19934
Conversation
@test broadcast!(() -> 0, V) == sparse(broadcast!(() -> 0, fV)) | ||
@test broadcast!(() -> 0, C) == sparse(broadcast!(() -> 0, fC)) | ||
@test let z = 0, fz = 0; broadcast!(() -> z += 1, V) == broadcast!(() -> fz += 1, fV); end | ||
@test let z = 0, fz = 0; broadcast!(() -> z += 1, C) == broadcast!(() -> fz += 1, fC); end |
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.
are you wanting to test the end values of z, fz?
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.
Only the contents of V
/fV
and C
/fC
(which implicitly test the end values of z
and fz
)?
(AV x86_64 failure unrelated.) |
Any thoughts on these semantics? If not, and absent objections or requests for time, I plan to merge this Monday morning PST. Best! |
Oh, I don't know why I missed this one, but actually, I do find the semantics a bit dubious: If |
Agreed on all points in principle :). Thoughts on a (presently realizable) better compromise between the generic |
I see the following options:
None of these are completely satisfying, my gut feeling is to prefer 4. |
The inlining of constants makes the present state really hairy: foo(p) = rand() < p ? rand() : 0.0
x = spzeros(100)
x .= foo.(0.1)
y = spzeros(100)
p = 0.1
y .= foo.(p) After this, |
The fact that it's not that hard to write code where that optimization ends up changing semantics like this makes me think it's not an unequivocally good thing to be doing in all cases. Why are we doing it in lowering instead of letting usual LLVM constant propagation optimizations do their thing more generally and safely? |
Background reading re.
Wonderful example :).
cc @stevengj. Best! |
For now I would lean towards option 1: making this edge case explicitly undefined. That gives us some leeway to figure out the right thing in the next release (which might still be letting it be ub). |
Option 5 would be to document that In algorithms with dense arrays, it's pretty useful to be able to do e.g. In algorithms with sparse arrays, using stochastic functions like this doesn't really make sense because they destroy sparsity, and using non-pure functions in general will defeat the ability to detect whether |
Option 5 would be option 3 with documentation. Getting that documentation right/exactly specifying the behavior is tricky, though. Consider x .= f.(a, b) For what combinations of sparse/dense But even with this sorted out in a reasonable way, I still think that if the semantics differ by input type even though the same semantics could be applied for all types, this is very likely a severe gotcha when writing generic code. |
Uncertainty about this is why I'm in favor of leaving this undefined until 1.0 – hopefully we'll figure out the right choice in the next several months and then we can make that the defined behavior. |
Of course, we could combine options 3 (or 5) and 4: Do something reasonable by default, but allow opting in to assume-pure or assume-non-pure behavior. x .= f.(a, b) # semantics type dependent
@assume_pure x .= f.(a,b) # may do all kinds of optimizations to reduce number of times f is called
@assume_non_pure x .= f.(a,b) # exactly length(x) calls to f in linear indexing order within a single thread These annotations would be macros (with better names, hopefully) that do the same transformations that lowering does, but replacing |
This pull request provides
broadcast!(f, C)
for sparse vector/matrixC
, with semantics close to those for generic two-argumentbroadcast!
. Specifically, this implementation checks at runtime whetherf()
yields zero. Iff()
does yield zero, this implementation emptiesC
and returns without additionalf()
calls. Iff()
does not yield zero, this implementation densifies and fillsC
via independentf()
calls. (@stevengj, thoughts on these semantics?) Best!