Constructing SVector with a loop

Suppose I want to construct an SVector with known length S and type T, but a loop (with a known number of iterations) would be the most comfortable way of doing it. What is the standard idiom for this in 1.0?

My problem is a bit complex, but for an example, consider

using StaticArrays

function SFib1(::Val{S}) where S
    values = Vector{Int}(undef, S)
    @inbounds for i in 1:S
        values[i] = (i == 1 || i == 2) ? 1 : values[i-1] + values[i-2]
    end
    SVector{S}(values)
end

Is it possible to allocate the SVector, then manipulate it with reinterpret or similar before returning it?

It looks like you’re looking for MVector?

I am not sure. Should I construct an MVector, then convert to an SVector? It appears to be slower than using a Vector, eg

function SFibM(::Val{S}) where S
    values = MVector{S, Int}(undef)
    @inbounds for i in 1:S
        values[i] = (i == 1 || i == 2) ? 1 : values[i-1] + values[i-2]
    end
    SVector{S}(values)
end

julia> @btime SFib1(Val(10));
  40.800 ns (1 allocation: 160 bytes)

julia> @btime SFibM(Val(10));
  65.488 ns (0 allocations: 0 bytes)

It seems the crossover point is 7.

julia> @btime SFibM(Val(7))
  31.316 ns (0 allocations: 0 bytes)

julia> @btime SFib1(Val(7))
  48.441 ns (1 allocation: 144 bytes)

without the additional conversion, I get:

julia> @btime SFibM2(Val(7)) 
  10.438 ns (1 allocation: 64 bytes)

Probably not a valid solution, but fun anyway:

function SFibNtuple(::Val{S}) where S
    val_i_minus_2 = Ref(1)
    val_i_minus_1 = Ref(1)
    tup = ntuple(S) do i
        if i == 1 || i == 2
            1
        else
            val = val_i_minus_2[] + val_i_minus_1[]
            val_i_minus_2[] = val_i_minus_1[]
            val_i_minus_1[] = val
        end
    end
    SVector(tup)
end

This infers up to Val(10).

using BenchmarkTools
@btime SFibNtuple(Val(10));

prints 1.702 ns (0 allocations: 0 bytes).

1 Like

You could use a generated function to unroll the loop.

Setfield.@set is a great tool for manipulating StaticArrays efficiently

using Setfield
using StaticArrays

function SFib1(::Val{S}) where S
    values = zeros(SVector{S, Int})
    @inbounds for i in 1:S
        values = @set values[i] = (i == 1 || i == 2) ? 1 : values[i-1] + values[i-2]
    end
    values
end
julia> @code_llvm SFib1(Val(4))

; Function SFib1
; Location: REPL[132]:2
define void @julia_SFib1_38655({ [4 x i64] }* noalias nocapture sret) {
L45.3:
; Location: REPL[132]:4
; Function macro expansion; {
; Location: /home/takafumi/.julia/packages/Setfield/X9IQb/src/sugar.jl:77
  %1 = bitcast { [4 x i64] }* %0 to <4 x i64>*
  store <4 x i64> <i64 1, i64 1, i64 2, i64 3>, <4 x i64>* %1, align 8
  ret void
;}
}
6 Likes

Neither a loop nor necessarily a good idea, but here is this:

function raise_(N) 
    if N == 0 
        :(args...)
    elseif N == 1   
        :(f(args...)) 
    else
        expr = raise_(N-1)
        return :(f($expr))
    end
end

@generated function raise(f, ::Val{N}, args...) where N
    raise_(N)
end

using StaticArrays, BenchmarkTools
fib(_) = (1, 1)
fib(t::NTuple) = (t..., t[end] + t[end-1])
fibs(::Val{N}) where N = SVector(raise(fib, Val(N-1), nothing))
; Function fibs
; Location: /Users/schauermr/.julia/v0.6/Bridge/project/ntuple.jl:20
define void @julia_fibs_38351({ [4 x i64] }* noalias nocapture sret) {
top:
  %1 = bitcast { [4 x i64] }* %0 to <4 x i64>*
  store <4 x i64> <i64 1, i64 1, i64 2, i64 3>, <4 x i64>* %1, align 8
  ret void
}