How to efficiently splat values into argument list?


#1

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.


#2

Please quote your code using backticks, and provide a self-contained example (with image, normals, etc).

The first think I would try is making normals an array of StaticArrays.SVector.


#3

If it’s all about getting rid of repeated logic, you can extract it to a method. It will be inlined, so won’t affect performance:

for I in CartesianIndices(image)
    rg = @view normals[:,I]
    t(c) = c*0.3+0.5
    image[I] = RGB(t(rg[1]), t(rg[2]), 0.5)
end

#4

Thanks, it’s good to know that functions like this have no overhead.