I’m trying to calculate the following:
where v and \hat{n} vectors of length 3. But I need to do this to a bunch of vectors stored in a Vector{Vector{SVector{3,Float64}}}
I ended up doing it like this:
dots = [[velocities[j][i]' * nHat[j][i] * nHat[j][i] for i =1:length(config.atomicBasis[j])] for j = 1:config.order]
velocities = velocities - 2 * dots
but wondering if there is a better way.
Thanks in advance
You’re allocating a bunch of temporary arrays, e.g. dots = [...]
is allocating lots of little arrays, 2 * dots
allocates new arrays, then velocities - 2 * dots
allocates more arrays.
Assuming you want to update velocities
in-place, it would be a lot clearer (and faster, with no temporary arrays) to just write a loop:
for j = 1:config.order, i = 1:length(config.atomicBasis[j])
@inbounds velocities[j][i] -= (2 * (velocities[j][i]' * nHat[j][i])) * nHat[j][i]
end
(Hopefully the compiler can hoist the velocities[j]
and nHat[j]
indexing out of the innermost loop?)
Loops (in local scope) are fast in Julia. Don’t twist yourself into a pretzel trying to use “vectorized” operations.
3 Likes
You could take the advice of stevengj (which is what you actually should do), or you could go julia-nuts:
using StaticArrays
using LinearAlgebra
new_vec(v::SVector, n̂::SVector) = v - 2*(v⋅n̂)*n̂
new_vec(v::Vector{<:SVector}, n̂::Vector{<:SVector}) = new_vec.(v, n̂)
new_vec(v::Vector{<:Vector{<:SVector}}, n̂::Vector{<:Vector{<:SVector}}) = new_vec.(v, n̂)
new_vec!(v::Vector{<:SVector}, n̂::Vector{<:SVector}) = v .= new_vec.(v, n̂)
new_vec!(v::Vector{<:Vector{<:SVector}}, n̂::Vector{<:Vector{<:SVector}}) = v .= new_vec!.(v, n̂)
vs = [rand(SVector{3, Float64}, 4) for _ ∈ 1:2]
n̂s = [rand(SVector{3, Float64}, 4) for _ ∈ 1:2]
vs = new_vec!(vs, n̂s)
I’ve noticed this theme emerge for me: I’ve been coding in python too long and always think I should write complicated code in the fewest number of lines possible. Would it be fair to say that the more julian thing do is often to just write out the dang loop?
Yes, Python trains you that your code is slow, and libraries are fast, so you should contort yourself to make all loops occur in libraries. To some extent, this is good advice in any language — re-using highly optimized, robust libraries is good!
But it has two limitations. First, you sometimes need to write really convoluted, opaque code to express your problem in terms of the library API. Second, you often pay a performance price for using a sequence of generic library functions instead of code that is specialized for your problem. (For example, you might need several “vectorized” calls instead of a single loop, which slows things down a lot. Julia’s “dot call” loop fusion can help with this, but not so much with nested data structures like yours.)
So, it’s a judgement call, but in Julia “writing out the dang loop” is a good alternative to writing convoluted “vectorized” code.
3 Likes