SVector vs struct performance

I am struggling to understand the huge performance difference I am seeing between these two approaches to creating a Vec3 DataType. I would prefer to use a SVector, but only if the performance is comparable. I feel like I must be missing a critical piece of the puzzle here.


Vec3 = SVector{3,Float64}

function test(v1, v2)
    v3 = Vec3(rand(), rand(), rand())
    for i = 1:10^7
        v3 = (v1 .* v2) .+ v3
    end
    return v3
end

v1 = Vec3(rand(), rand(), rand())
v2 = Vec3(rand(), rand(), rand())

test(v1, v2)
@time v3 = test(v1, v2)

println(v3)

Result: 2.440927 seconds (40.00 M allocations: 2.384 GiB, 9.92% gc time)

struct Vec3
    x::Float64
    y::Float64
    z::Float64
end

add(a::Vec3, b::Vec3)::Vec3 = Vec3(a.x+b.x, a.y+b.y, a.z+b.z)
mul(a::Vec3, b::Vec3)::Vec3 = Vec3(a.x*b.x, a.y*b.y, a.z*b.z)

function test(v1, v2)
    v3 = Vec3(0,0,0)
    for i = 1:10^7
        v3 = add(mul(v1, v2), v3)
    end
    return v3
end

v1 = Vec3(rand(), rand(), rand())
v2 = Vec3(rand(), rand(), rand())

test(v1, v2)
@time v3 = test(v1, v2)

println(v3)

Result: 0.009356 seconds (1 allocation: 32 bytes)

Thanks,
Arnie

1 Like

const Vec3 = SVector{3,Float64} fixes this for me. The problem is that without the const, test can’t know that the definition of Vec3 won’t change at any time. This makes the whole method type unstable.

5 Likes

Your Vec3 alias is a non-constant global variable in your first example. Marking that as const Vec3 = ... should fix the issue.

This isn’t necessary in your second example because struct definitions are always constant and therefore don’t need to be marked const

2 Likes

That did the trick! Thank you both for your help.

Off topic: you can use the splat operator to initialize those vectors:

Actually, the above first creates a length-3 standard array, then splats, which can be very expensive.

This is much faster and more convenient:

v1 = rand(Vec3)

(>20x as fast and zero allocations on my laptop, this is the same speed as Vec3(rand(), rand(), rand()), BTW.)

4 Likes

True, but that does not work for the custom struct Vec3. (And, I agree, even in that case if speed is a concern, splatting is not the best choice).

In this particular example, the struct Vec3 may subtype FieldVector and rand will work as expected.

julia> struct Vec3{T} <: FieldVector{3,T}
           x::T
           y::T
           z::T
       end

julia> rand(Vec3{Float64})
3-element Vec3{Float64} with indices SOneTo(3):
 0.8851737855718469
 0.47765777403880194
 0.6473023269774472

This won’t work in general though.

1 Like

It works if Vec3 is an alias for SVector, which the OP actually wanted to use.

A simple fix for the custom type is

julia> Base.rand(::Type{Vec3}) = Vec3(rand(), rand(), rand())

julia> rand(Vec3)
Vec3(0.06672577632910204, 0.7133141256121924, 0.6727958470918942)

but it is a bit more work to get the full rand functionality, like e.g. rand(Vec3, 3, 4) or setting the rng.

2 Likes