Copy array elements by index

Hi,
MWE:

function y_from_x!(y, x)
    interesting_indices = [7, 2, 4]
    for i in keys(interesting_indices)
        y[i] = x[interesting_indices[i]]
    end
end
a=collect(2:9)
b=zeros(3)
y_from_x!(b,a)

then b is [8.0,3.0,5.0] as expected

I would like to write this cleaner and more importantly with better performance (for bigger “interesting_indices”, x and y vectors)
I already found y[:] = getindex(x, interesting_indices), but getindex allocates a new array, right? that would be bad performancewise, right?
Any ideas?
Also: would it be better to make the interesting_indices a const? they wont change…
Thanks

Welcome to Discourse!

I don’t think you can really improve the performance of this much from where you are, short of not allocating the interesting_indices array every call. For example, you could use a tuple interesting_indices = (7,2,4) to not allocate.

You are correct that getindex allocates a new array when called for an array of indices. But you can use broadcasting over getindex to perform it all in terms of scalar getindex (which does not allocate):

y .= getindex.(Ref(x), interesting_indices)

Broadcasting is not intrinsically faster than a loop, but it does tend to avoid excess boundschecks that might otherwise might happen if you aren’t careful in writing the loop.

2 Likes

Let me add some timings:

a = collect(2:9);
b = zeros(3);
I = [7, 2, 4]

function f!(y, x, i)
    y[:] = x[i]
end

function g!(y, x, i)
    @views y[:] = x[i]
end

function h!(y, x, i)
    y .= @view x[i]
end

julia> @btime f!($b, $a, $I)
  63.545 ns (1 allocation: 80 bytes)
julia> @btime g!($b, $a, $I)
  29.778 ns (0 allocations: 0 bytes)
julia> @btime h!($b, $a, $I)
  26.773 ns (0 allocations: 0 bytes)
4 Likes

cool, thanks!
so i guess with

const interesting_indices = [7,2,4]

function y_from_x!(y, x)
    y .= @view x[interesting_indices]
end

I will get the best performance and clean code. Thanks to the two of you!

Having interesting_indexes as a global variable may limit your flexibility and/or performance. Maybe you just need to pass this array as a parameter. Also you can write that in one line, if you want it to be concise:

y_from_x!(x,y,interesting_indexes) = y .= @view x[interesting_indexes]

If you have to create y for every new set of interesting indexes, than the fastest way to do it is something like:

y_from_x!(x,y,interesting_indexes) = y .= @view x[interesting_indexes]

function y_from_x(x,interesting_indexes)
    y = similar(x, length(interesting_indexes))
    return y_from_x!(x,y,interesting_indexes)
end
1 Like

I don’t know if clear enough, but very efficient.
it can be even more so if the index vector is made up of consecutive index blocks

foreach(e->copyto!(b,e, a,I[e], 1), eachindex(b))