I’ve found that using generator syntax in argument lists seems to cause unwanted allocations.
For example, I have a piece of code that does something very simple, taking the slices of a 3D array and turning them into RGB colors in a 2D array (with some transformation). The code below performs fine with no allocations.
array = randn(100, 100)
function display_array(image, array)
for I in CartesianIndices(image)
rg = @view array[:,I]
image[I] = RGB(rg[1]*0.3+0.5, rg[2]*0.3+0.5, 0.5)
end
return image
end
function display_array(array)
image = Array{RGB{eltype(array)}}(undef, size(array)[2:end]...)::Array{RGB{eltype(array)}, 2}
return display_normals(image, array)
end
image = display_array(array)
But when I change this to use a generator to reduce the repeated code,
function display_array(image, array)
for I in CartesianIndices(image)
rg = @view array[:,I]
image[I] = RGB((c*0.3+0.5 for c in rg)..., 0.5)
end
return image
end
There are tens of thousands of allocations happening in the inner loop. I’ve tried using list comprehensions and using broadcasting math on the slice directly but all of these result in more allocations. I can’t use a loop because I’m generating values to be fed into the constructor RGB and don’t want to allocate an array for these values.
Is metaprogramming the only solution to write performant and non-repetitive code in an example like this? Is there a macro equivalent to generators/comprehensions?
EDIT: At the suggestion of others, I wrote a self-contained example, also removed references to normals since it’s not relevant.