I'm new to Julia and I was testing my understanding by the following benchmarks of three equivalent ways to set all elements of an Array smaller than 0.5 to 0.:
using BenchmarkTools
function test!(A)
@btime begin # method 1
mask = $A .< 0.5
$A[mask] .= 0.
end
@btime begin # method 2
$A[$A .< 0.5] .= 0.
end
@btime begin # method 3
@inbounds begin
for i in eachindex($A)
if $A[i] < 0.5
$A[i] = 0.
end
end
end
end
end
n = 1000
test!(rand(n,n))
This outputs
1.612 ms (13 allocations: 3.94 MiB)
1.619 ms (13 allocations: 3.94 MiB)
4.215 ms (0 allocations: 0 bytes)
Based on what I have read about Julia until now I have several questions:
- Why is method 3 the slowest? I'd expect it to be the fastest, since it avoids any heap allocations. I tried to look at the LLVM/native code output but that went over my head.
- Why are method 1 and 2 equally fast? For me method 1 is the "Python/Numpy method", where allocating an intermediate array for storing the mask is a typical (unwanted) byproduct of vectorized Numpy programming. I had expected Julia to optimize method 2 into method 3 through the
.operator. - The expected behavior is to have no allocations and near-C fast performance, preferably without an explicit for-loop. How can I achieve this?
Thank you for your time.