I am trying to understand what is the best strategy to initialize arrays in Julia. Consider this small example:
using BenchmarkTools
function f(theta)
[cos(theta) * sin(theta), sin(theta), 1.0]
end
function g(theta)
result = Array{Float64}(undef, 3)
result[1] = cos(theta) * sin(theta)
result[2] = sin(theta)
result[3] = 1.0
result
end
function runtest()
bf = @benchmark f(0.1)
bg = @benchmark g(0.1)
println("f(x, y): $(bf.allocs) allocations, $(bf.memory) bytes")
println("g(x, y): $(bg.allocs) allocations, $(bg.memory) bytes")
end
runtest()
This is the output (using Julia 0.7-alpha on a 64-bit Linux system):
f(x, y): 3 allocations, 144 bytes
g(x, y): 1 allocations, 112 bytes
I don’t understand why f
uses more memory than g
: to me, they seem absolutely equivalent. I tried to use @code_typed
and @code_warntype
to shed some light, but everything looks ok to me:
julia> @code_warntype f(0.1)
Body::Array{Float64,1}
4 1 ─ %1 = invoke Main.cos(%%theta::Float64)::Float64 │
│ %2 = invoke Main.sin(%%theta::Float64)::Float64 │
│ %3 = Base.mul_float(%1, %2)::Float64 │╻ *
│ %4 = invoke Main.sin(%%theta::Float64)::Float64 │
│ %5 = invoke Base.vect(%3::Float64, %4::Vararg{Float64,N} where N, 1.0)::Array{Float64,1} │
└── return %5 │
julia> @code_warntype g(0.1)
Body::Array{Float64,1}
8 1 ─ %1 = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{Float64,1}, svec(Any, Int64), :(:ccall), 2, Array{Float64,1}, 3, 3))::Array{Float64,1}
9 │ %2 = invoke Main.cos(%%theta::Float64)::Float64 │
│ %3 = invoke Main.sin(%%theta::Float64)::Float64 │
│ %4 = Base.mul_float(%2, %3)::Float64 │╻ *
│ Base.arrayset(true, %1, %4, 1) │╻ setindex!
10 │ %6 = invoke Main.sin(%%theta::Float64)::Float64 │
│ Base.arrayset(true, %1, %6, 2) │╻ setindex!
11 │ Base.arrayset(true, %1, 1.0, 3) │╻ setindex!
12 └── return %1 │
Why is g
slightly more efficient than f
?