4 ms·
This very much depends upon the CPU upon which the code is running. The 'shift' vs 'multiply' (or add to self) for doubling came about because in the past, it
by pwg 7y ago
This very much depends upon the CPU upon which the code is running.
The 'shift' vs 'multiply' (or add to self) for doubling came about because in the past, it was very common that shifts were faster than multiplies (if your CPU even had a multiply instruction) and often faster than adds as well.
Example, from the 8086 (yes, very long time ago, but this is the environment where the differences often massively mattered):
https://www.oocities.org/mc_introtocomputers/Instruction_Timing.PDF https://www.oocities.org/mc_introtocomputers/Instruction_Tim...
Add reg->reg: 3 clock cycles
Mul: 70-133 depending on 8 vs. 16 bit size
Shift: Reg with shift of 1 (which is a *2): 2 clock cycles.
Now, for divide by 2 the issue is even larger (as you can't 'subtract from itself' to achieve divide by 2):
Idiv: 101-184 clocks, depending on 8 vs. 16 bit size
Shift: 2 clock cycles.
So, on the 8086, for times 2, a shift was 33% faster than an add to self (and so much faster than a Mul that no one should use Mul for times 2).
And for divide by 2, a shift was massively faster than an Idiv (2 cycles vs minimum of 101 cycles).
Now, these relative values change as one moves up the x86 CPU line to newer CPU's. Intel built faster adders, faster multipliers, faster dividers, so one really has to check the specific CPU to see which instruction is faster. But the one item that will remain fairly constant is that presuming that using a shift for powers of two multiply or divide is generally close to the 'fastest' method is a good ball-park estimate that is more often right than it is wrong.
- remcob 7y agoIn the article, there's a whole path from source to JVM bytecode to ARM assembly. With auxiliary goals like bytecode size. This makes it interesting to include add-double because it has a simple bytecode encoding ('DUP ADD' compared to 'PUSH 2 MUL') and it is interesting to see what the several compilers do with it along the way in terms of optimization. Interesting info on 8086. Another approach that doesn't apply to OP's article, but does to x86 assembly is (ab)using LEA for small multiplications. At 2 clock cycles it looks competitive with shift for doubling, but can also be used for multiples like 3 and 5.
- spc476 7y agoSHL/SAL/SHR/SAR (the shifts) affect the condition codes so one could check for overflow (or use it as part of multibyte arithmetic) while LEA does not affect the conditions code (so overflow goes undetected). It's something else to keep in mind.
- remcob 7y agoIndeed! And this is not necessarily a downside, sometimes you want to preserve the flags. Intell added the MULX instruction that is similar to MUL, except that it doesn't touch the flags. It's very useful in bignum math.