Possible to do non-allocating Setfield operations on OffsetArray(::StaticVector)?

I have some code which initializes an array with a loop in a way where each element depends on the last element. I’m trying to make this work with StaticVectors so it can be non-allocating. I’ve found that I can use Setfield.@set!(since I need to depend on the previous elements) and that part seems to work fine, e.g.

function foo(x₁)
    x = @SVector(zeros(Int,5))
    @set! x[1] = x₁
    for i=2:5
        @set! x[i] = 2*x[i-1]+1
    end
    x
end
@code_llvm foo(0)

generates what to my naive eye looks like optimal code (which, in a lot of ways, is already pretty amazing to me given what Setfield does; Julia 1.6, btw).

For code clarity in my real case, I also want to have this be an OffsetArray though. Something like:

function offset_foo(x₀)
    x = OffsetVector(@SVector(zeros(Int,5)), 0:4)
    @set! x[0] = x₀
    for i=1:4
        @set! x[i] = 2*x[i-1]+1
    end
    x
end

This allocates unfortunately. I think it may related to bounds checking in OffsetArray.get/setindex, but I can’t figure out how to turn it off. Pehaps unsurprisingly, neither of

@set! @inbounds x[0] = x₀
@inbounds @set! x[0] = x₀

seem to work (one is an error, the other doesn’t do anything).

Any ideas how I can turn off bounds checking here? Or if there’s other better approaches to solving this problem?

EDIT: Actually sorry I don’t think its the boundschecking, its just that @set! is turning the OffsetArray(::StaticVector) into a OffsetArray(::Vector). Still welcome any suggestions for a way around this though.

This behavior might get closer to what you want after OffsetArrays.jl/pull/213 is merged. After this,

julia> offset_foo(1)
5-element OffsetArray(::MArray{Tuple{5},Int64,1,5}, 0:4) with eltype Int64 with indices 0:4:
  1
  3
  7
 15
 31

The return type is an offset MArray, so it is static. It’s not allocation free though, but allocations do come down.

Before:

julia> @btime offset_foo(1);
  337.063 ns (5 allocations: 640 bytes)

After:

julia> @btime offset_foo(1);
  134.921 ns (5 allocations: 240 bytes)
2 Likes