Faster Unsigned Division by Constants (2011)
ridiculousfish.com
ridiculousfish.com
The short answer is that they all do it well. For a loop like this compiled with -O3:
for (int i = 0; i < COUNT; i++) {
compilerSum += randomInt[i]/CONST;
}
ICC 14.0.3 takes 4.1 cycles per iteration, Clang 3.4.1 takes 3.8 cycles, and GCC 4.8.1 manages to spend only 1.3 cycles for each division!Apparently GCC realized it was able to not only use a shortcut for the division, but also managed to vectorize the loop. Here's the inner loop it produced:
4019f0: 66 0f 6f 00 movdqa (%rax),%xmm0
4019f4: 48 83 c0 10 add $0x10,%rax
4019f8: 48 39 c5 cmp %rax,%rbp
4019fb: 66 0f 6f c8 movdqa %xmm0,%xmm1
4019ff: 66 0f 6f e0 movdqa %xmm0,%xmm4
401a03: 66 0f 62 c8 punpckldq %xmm0,%xmm1
401a07: 66 0f 6a e0 punpckhdq %xmm0,%xmm4
401a0b: 66 0f f4 cb pmuludq %xmm3,%xmm1
401a0f: 66 0f f4 e3 pmuludq %xmm3,%xmm4
401a13: 0f c6 cc dd shufps $0xdd,%xmm4,%xmm1
401a17: 66 0f fa c1 psubd %xmm1,%xmm0
401a1b: 66 0f 72 d0 01 psrld $0x1,%xmm0
401a20: 66 0f fe c1 paddd %xmm1,%xmm0
401a24: 66 0f 72 d0 02 psrld $0x2,%xmm0
401a29: 66 0f fe d0 paddd %xmm0,%xmm2
401a2d: 75 c1 jne 4019f0 <main+0xb0>
I put the other two here: https://gist.github.com/nkurz/b1c3e01498dd11fda42fI didn't look closely enough to identify which methods they were using. Can someone else tell?
; xmm7 = 0 magic 0 magic
; xmm6 = 0
; xmm5 = 0 (accumulator)
division_loop:
vmovdqu xmm4, [rax] ; xmm4 = w3 w2 w1 w0
vpunpckldq xmm0, xmm4, xmm6 ; xmm0 = 0 w1 | 0 w0
vpunpckhdq xmm2, xmm4, xmm6 ; xmm2 = 0 w3 | 0 w2
vpmuludq xmm1, xmm0, xmm7 ; xmm1 = w1 * magic | w0 * magic
vpmuludq xmm3, xmm2, xmm7 ; xmm3 = w3 * magic | w2 * magic
vpaddq xmm1, xmm1, xmm0 ; xmm1 = w1 * magic + w1 | w0 * magic + w0
vpaddq xmm3, xmm3, xmm2 ; xmm3 = w3 * magic + w3 | w2 * magic + w2
vpsrlq xmm1, xmm1, XX ; XX = shift constant
vpsrlq xmm3, xmm3, XX ; XX = shift constant
vshufps xmm1, xmm1, xmm3, 10001000b ; back to w3 w2 w1 w0
vpaddd xmm5, xmm5, xmm1 ; add to accumulator
add rax, 16
cmp rax, rbp
jnz division_loop
The shifts can be further tweaked to allow to use a blend instead of a shuffle, in case there's a penalty for changing from integer to floating-point domains. Saturating increment also works well, despite there not being a PADDUSD instruction; it can be done in two cycles: ; xmm6 = -1 -1 -1 -1
vpcmpeqd xmm3, xmm4, xmm6 ; xmm3[i] = (xmm4[i] == -1 ? -1 : 0)
vpsubd xmm4, xmm4, xmm6 ; xmm4[i] += 1
vpaddd xmm4, xmm4, xmm3 ; xmm4[i] += xmm3[i]I don't know if you will have a domain penalty for the vshufps, but I think the answer is yes, and that the blend would be better. The blend can also run on p015, versus only p5 for the shufps, so it might pipeline better. I hadn't realized until now that vshufps had different semantics than vpshufd and is able to combine from two different registers. If there is no penalty, this could be quite a useful instruction.
Any reason for using 'w' for your 32-bit elements rather than 'd' for double-word as used in the instruction names?
I chose 'w' for no real reason other than being thinking of 'word' at the time.
I wrote up a few solutions many years ago, one of which is Sree Kotay's idea: adding one to the numerator (what this article says but without the awesome proof): http://stereopsis.com/doubleblend.html
It would be great if compilers could do all this for you, with a proof like this to go with it.
((0xFF*0xFF = 0xFE01) + 0xFE + 0x80 = 0xFF7F) >> 8 = 0xFF
((0xC0*0xC0 = 0x9000) + 0x90 + 0x80 = 0x9110) >> 8 = 0x91
((0xB4*0xB5 = 0x7F44) + 0x7F + 0x80 = 0x8043) >> 8 = 0x80
((0xB4*0xB4 = 0x7E90) + 0x7E + 0x80 = 0x7F8E) >> 8 = 0x7F
((0x80*0x80 = 0x4000) + 0x40 + 0x80 = 0x40C0) >> 8 = 0x40
((0x00*0x00 = 0x0000) + 0x00 + 0x80 = 0x0080) >> 8 = 0x00
(To be precise, you should also add the MSB to the right of the LSB, etc., since 1/.FF = 1.01010101..., but now you're getting lost in the noise.)EDIT: screwed up some math; thanks gjm11.
(Multiplying the RHS by FF gives FF.FFFFFFFFFF etc. = 100. 1.020304... is 1/(.FF)^2.)
https://github.com/D-Programming-Language/dmd/blob/master/sr...
The general conclusion is that simple operations like add can be done in 1 CPU cycle. They can be easily pipelined for a throughput of 3 different add operations in 1 cycle. Modern CPUs also pipeline integer multiply so, while the latency is perhaps 4 cycles, the throughput is 1 multiply in 1 CPU cycle.
But hardware division can be slow, really really slow. A Pentium 4 might have a latency of 80 cycles for a 32 bit operation, 160 cycles for a 64 bit operation. And there's no pipelining, multiple divides don't appear to be done in parallel. Even newer CPUs like the Sandy Bridge architecture have a latency of 26/92 cycles for 32/64 bit operations.
So it's not surprising that compilers try to avoid doing hardware divide.
I suppose that part of this is that we now have 64-bit operations everywhere - the length of the critical path for a 64-bit divide is absurd.
The division is transformed into multiplication and a right shift.
I don't think your usual programmer must care about that with the modern compilers, but it is nice to know if you are implementing one or you are coding in asm.
It does not seem to appear in the literature, nor is it implemented in gcc, llvm, or icc, so fish is optimistic that it is original.
I agree compilers have not used such algorithms for numbers bigger than a register "for a long time". But bignum libraries certainly have.
Note that I did not say "compilers implemented precisely what fish implemented" for a long time, but compilers did "this sort of thing" for a long time.
There's another paper referred to by the original one I cited, namely this one http://dl.acm.org/citation.cfm?id=4990 (1985) which gives lots of algorithms for division by specific integers.
Alverson also developed algorithms for division by more problematic divisors in 1991:
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.33.1...
This was in fact implemented in hardware in the 64 bit Terra computer and was the work that inspired Montgomery and Granlund. In about 1994 they implemented their algorithms in gcc.
The important point I was making is that the Montgomery-Granlund work does not rely on "magic numbers" (divisors of 2^B +/- 1).
In fact, in a certain sense, such "algorithms" go back hundreds of years. If you look in Dixon's History of Number Theory under "divisibility rules", you'll essentially find dozens of rules, which can be adapted for any base (where "base" could mean 2^32, 2^64, 2^128, 2^(32*n), etc.).
Many divisibility rules basically just consist of an algorithm for computing the remainder quickly and computing the quotient quickly, using a small number of additions, subtractions and multiplications by small constants.
That's what I was referring to when I said "this sort of thing".
I give a bunch of such rules here: http://wbhart.github.io/SimpleMath/divisrules.html
where obviously there I give the rules for base 10. But some of them were even originally stated for general bases.
However, 7 does divide 2^33 - 1. This "magic number" does fit in a 32 bit word. It's just that division by 2^33 - 1 isn't as trivial as division by 2^32 +/- 1 (it's not much worse though).
Multiplication by/storage of a 33 bit number also isn't a problem anyway, because you can use the "IEEE trick" of storing the leading 1 bit implicitly. Multiplication by such a number can be done with one multiply and one addition. Not that you actually need to do this for the divisor 7.
The original Montgomery-Granlund work (apparently the basis of GCC division by invariant integers for quite a while) was to always find a quasi-"magic number" which fits in a word. So that was definitely not the innovation of Fish.
The followup paper of Moller-Granlund does even better by using a mullow instead of a full mul or mulhigh, which is usually available sooner on modern processors.
Fish seem to have found some highly optimised sequences. I'm not entirely clear on how new the ideas themselves are. It's clearly not the same as Montgomery-Granlund, but Montgomery-Granlund use (B^2 - 1)/d (where admittedly d has to be first "normalised"), Fish uses (2^p*B)/d where they fiddle the dividend and do some post-shifting.
I'm not even sure if Fish is better than Moller-Granlund, though the latter was 2009, not 1994.
I don't want to take away from the contribution of Fish. But it just needs to be viewed in the context of rather a lot of previous work.
https://github.com/llvm-mirror/llvm/blob/master/lib/Transfor...
https://github.com/llvm-mirror/llvm/blob/master/lib/Transfor...