This is my first major project in Julia and I have a core function that will be evaluated many times that I want to optimize. The function accepts a vector and returns a vector, but I only anticipate using it with vectors of 2-6 units. There’s an inner function which I define to accept only Float64
scalars:
F(x::Float64, a::Float64) = 1.2*exp(-a*x)
The simplest, most MATLAB-like definition of the function can be done in one line:
f(x,a) = vcat(1, F.(cumsum(x),a))
However, I’ve found significant performance improvements by defining additional methods for different-sized inputs as tuples rather than vectors, e.g.:
function f(x::Float64, a::Float64)
y₁ = 1
y₂ = F(x,a)
return (y₁, y₂)
end
function f(x::NTuple{2,Float64}, a::Float64)
y₁ = 1
y₂ = F(x[1],a)
y₃ = F(x[1]+x[2],a)
return (y₁, y₂, y₃)
end
function f(x::NTuple{3,Float64}, a::Float64)
y₁ = 1
y₂ = F(x[1],a)
y₃ = F(x[1]+x[2],a)
y₄ = F(x[1]+x[2]+x[3],a)
return (y₁, y₂, y₃, y₄)
end
The speed/memory differences are significant:
julia> using BenchmarkTools
julia> @btime f([0.1,0.2],0.5);
1.313 μs (19 allocations: 1.00 KiB)
julia> @btime f((0.1,0.2),0.5);
13.197 ns (0 allocations: 0 bytes)
Since I’m still learning the rope of Julia, I’d appreciate any feedback on two points:
- Is this the most idiomatic way to do something like this in Julia? Are there some nuances of the compiler I’m missing that would make all of this unnecessary?
- If this is the correct way to do things, would it be an acceptable use of metaprogramming to define these methods rather than having everything typed out in the module? If so, what would be a good way to accomplish that?
Looking forward to learning how to use and understand a beautiful language!