For example, let’s compare your method:
function doit(mu, V, n)
X = rand(MvNormal(mu, Hermitian(V)), n)
foreach(normalize!, eachcol(X))
return X
end
to a version that expects static arrays, and returns an array of 3-component SVectors
instead of a matrix:
using StaticArrays
function doit(mu::SVector, V::SMatrix, n)
L = cholesky(V).U'
X = [normalize(mu + L * @SVector(randn(3))) for _=1:n]
return X
end
On my machine, I find that
mu_s = SA[2., 2., 2.]
V_s = SA[0.74 -0.08 0.34; -0.08 1.57 -0.5; 0.34 -0.5 1.16]
@btime doit($mu_s, $V_s, $n);
is almost twice as fast as @btime doit($mu, $V, $n);
and if you pre-allocate X then you gain another 10%.
(And, as I said above, it may be even better if you merge this loop with any additional processing you want to do on these random vectors, rather than the traditional “vectorized” style of doing many individual passes over the data with built-in “vectorized” operations, which is only necessary in languages where loops are slow.)
Note that this implementation is essentially pulling out the internals of the MvNormal
algorithm: if V has the Cholesky factorization V = L L^*, and r = randn(3)
, then x = \mu + L r has mean \mu and covariance V.