Hi,
I am designing a bunch of operators I would like to evaluate lazily on sets of points (grid cells). The problem is that my design pattern still allocates a bit more when broadcasting, and involves me extending the broadcasting behaviour, which I am not very familiar with. Ideally, in the end result I just want to loop once through the grid cells to apply these operations. I am testing the for loop and broadcasting versions and they are worse than a simple function.
The operators are something like that:
import Base.Broadcast: broadcasted
import Base
using BenchmarkTools
struct InsideSheetOp
xmin::Real
xmax::Real
function InsideSheetOp(xmin::Real, xmax::Real)
# do initialisation stuff here in more complicated cases
new(xmin, xmax)
end
end
function (op::InsideSheetOp)(x::Real)
if (x < op.xmin) || (x > op.xmax)
return false
end
return true
end
Base.broadcastable(op::InsideSheetOp) = Ref(op)
# Define broadcasting behavior
function Base.Broadcast.broadcasted(op::InsideSheetOp, x)
#Probably I want in-place operations but I am testing it this way for now
mask = similar(x, Bool)
@. mask = !((x < op.xmin || x > op.xmax))
return mask
end
for example, comparing with a simple implementation that I broadcast:
function in_sheet(x::Real, xmin::Real, xmax::Real)
if (x < xmin || x > xmax)
return false
else
return true
end
end
I get for a somewhat large grid:
x = collect(0:0.1:100000)
opsheet = InsideSheetOp(0, 1000)
@benchmark opsheet.($x) #1.2 ms 18 allocs (~1Mb)
@benchmark in_sheet.($x, Ref(0), Ref(1000)) # 296 ΞΌs 4 allocs (~100kb)
And a for loop allocates also:
@benchmark @inbounds for i in eachindex($x)
$mask[i] = in_sheet($x[i], 0, 1000) # 0 allocs
end
mask2 = similar(x, Bool)
@benchmark @inbounds for i in eachindex($x)
$mask2[i] = opsheet($x[i]) # lots of allocs
end
Am I overcomplicating things, here? Can anyone suggest improvements or a different approach? Why my functions allocate so much like this?
Thank you.