The trick here is that you have to understand what each tool is doing before you use it, and that's the opposite of what computer languages are driving towards these days. There is a big push towards abstracting away the actual work, potentially so the compiler can apply optimizations under the hood, but this requires the programmer to either know tons of details about what the compiler does, or to trust it implicitly and hope they don't write code that is grossly inefficient. Or maybe what you do is grossy inefficient, but it can be automatically parallelized well so you don't notice it too much.
Making the choice to work with collections that can be accessed in O(log n) rather than O(n^2) is a trivial optimization.
Maybe you don't need to scope out every subroutine, but taking the time to review the basic performance features is something we can all strive to do.
"Optimization" means "making code run faster without changing what it does". If you're changing the code's behavior in an observable way, that's not optimization.
Keywords: small efficiencies.
How it's usually used: I shouldn't think about performance up front at all.
Nothing teaches this better than seeing code making thousands of ephemeral objects in Java in a heap profiler and getting GC stalls with no big place to fix.
The problem begins where deadlines are too close to fix minor inefficiencies...