Motivation
lazy_broadcast
is useful in a few situations:
Improved expressibility + fusing operations
Debugging broadcast machinery
Delaying execution of a broadcast expression
Improved expressibility + fusing operations
You may have an implimentation of broadcast that involves a matrix-vector multiplication, suppose we have two operators, foo_op
and bar_op
has_foo_model && @. x += 2 * foo_op(y)
has_bar_model && @. x += 3 + bar_op(y)
and if foo_op
and bar_op
are broadcasted in an implementation-specific way, you may not be able to (easily) write this to leverage dispatch:
foo(y, ::NoFooModel) = 0
foo(y, ::HasFooModel) = 2 * foo_op(y)
bar(y, ::NoBarModel) = 0
bar(y, ::HasBarModel) = 3 + bar_op(y)
@. x += foo(y, model) + bar(y, model)
since foo_op
and bar_op
may need to exist in the broadcasted expression. It turns out that this is pretty easy to do with LazyBroadcast:
foo(y, ::NoFooModel) = 0
foo(y, ::HasFooModel) = @lazy @. 2 * foo_op(y)
bar(y, ::NoBarModel) = 0
bar(y, ::HasBarModel) = @lazy @. 3 + bar_op(y)
@. x += foo(y, model) + bar(y, model)
This form has some advantages:
- we've fused the reads of vectors
x
andy
, which can result in notably better performance - we've not duplicated logic (much). We could potentially use union splitting to achieve fusion, but this can become unwieldy as the number of combinations of cases increases.
- the new functions would be in functional form, and more easily unit-tested
Debugging broadcast machinery
If you overload Julia's broadcast software layer, then you may find yourself constructing broadcast objects and working with them to write unit test on your overloaded implementation of broadcast. We've often found it more convenient to construct Base.Broadcasted
objects using the standard dot-syntax + LazyBroadcast. Here is an example, instead of writing:
x = [1, 2]
y = [1, 2]
a = Base.Broadcast.instantiate
(Base.Broadcast.broadcasted(+ x, y))
You can write
using
LazyBroadcast: lazy_broadcast
x = [1, 2]
y = [1, 2]
a = lazy_broadcast.(x .+ y)
which is typically the more common form that broadcast expressions exist in applications.
Delaying execution of a broadcast expression
Another interesting use-case of LazyBroadcast is to use it for delaying execution.
For example, we can write an expression:
using LazyBroadcast: lazy_broadcast
a = [0, 0]
b = [0, 0]
c = lazy_broadcast.(a .+ b)
nothing
Perform calculations
a .= [1, 1]
b .= [1, 1]
nothing
And then finally evaluate the expression:
Base.materialize(c)
2-element Vector{Int64}:
2
2