Rule 1. You can't tell where a program is going to spend its time. Bottlenecks occur in surprising places, so don't try to second guess and put in a speed hack until you've proven that's where the bottleneck is.
Rule 2. Measure. Don't tune for speed until you've measured, and even then don't unless one part of the code overwhelms the rest.
For example if I am writing a toy neutral network with a small dataset I don't care how optimized it is, or if it runs slowly on the CPU.
But when training a large network on a large amount of data it is well worth spending extra effort from the start to ensure as much work as possible is done on a GPU and writing it to ensure if can support multiple GPUs.
If you have a huge data set and some understanding what you're doing, the bottlebecks will be pretty obvious.
Pike point is not just to avoid premature optimization. It’s to measure bottlenecks. Because due to changing language and hardware developments, what you think you knew to be true might become outdated.
In any case, there is a reason why Python has a profiler in the box, as what one thinks and what actually is, isn't the same. Which was my starting point.
julia> @code_llvm debuginfo=:none 5.0 + 3
define double @"julia_+_156"(double %0, i64 signext %1) #0
{
top:
%2 = sitofp i64 %1 to double
%3 = fadd double %2, %0
ret double %3
}
julia> @code_native debuginfo=:none 5.0 + 3
...
vcvtsi2sd %rdi, %xmm1, %xmm1
vaddsd %xmm0, %xmm1, %xmm0
retq
...For those who want to avoid it and still use a loop, you can create a vector beforehand with the final length and fill it in. If you don't know the final length, create a vector with a good guess for length, double its length whenever it gets full, and then crop off the unused tail when you're done.