Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This post misses one of the most fundamental and important optimisations for 'digits10' that his compiler is likely doing for him: turning the divide by 10 in to a multiply by using a multiplicative inverse mod 2^64

    $ cat div10.cpp 
    #include <cstdint>

    uint64_t div10(uint64_t y) {
        return y / 10;
    }

    $ g++ -std=c++11 -march=native -Ofast -c div10.cpp
    $ objdump -C -d --no-show-raw-insn div10.o

    0000000000000000 <div10 (unsigned long)>:
    0:   movabs $0xcccccccccccccccd,%rdx
    a:   mov    %rdi,%rax
    d:   mul    %rdx
    10:   mov    %rdx,%rax
    13:   shr    $0x3,%rax
    17:   retq
His basic implementation likely isn't using division at all.


Doesn't this quote from the article address that? Or was it a late add?

"Truth be told, it's a multiplication because many compilers transform all divisions by a constant into multiplications; see e.g. http://goo.gl/LhPeH "


Guess I missed it. Interestingly it seems to be impossible to do this optimisation yourself in C or C++ and get either Clang or GCC to generate the same machine code (on x86-64).


It is possible to coax that codegen out of clang. The trick is that you need the high half of the product, so you must use a larger type, in this case __uint128_t:

    uint64_t div10(uint64_t y) {
        const uint64_t magic = 0xCCCCCCCCCCCCCCCDULL;
        __uint128_t prod = magic * (__uint128_t)y;
        return (uint64_t)(prod >> (64 + 3));
    }
See http://libdivide.com for how to get this codegen with runtime constants (I am the author).


Clang since at least v3.0 has done this automatically at -O1 and above - no need to use this cool, but ungainly, and hard-to-maintain approach.

http://goo.gl/Cx0E0c


GCC has done this for 20 years... the point is this optimisation requires a wider integer type than 64bits (in this case, apparently, 67 bits). This optimisation is potentially important for anything generating bytecode, or native code inside a JIT.


Thanks a lot for this work, btw; I found your labor of division blog posts last year and integrated your code into my permutation generation logic, to very nice effect.


Why did you create libdivide, and how long did it take you to grasp the algorithm and produce a correct implementation?


The algorithm was esoteric, confined to compilers. But there was no technical reason why it could not be implemented in a library: just nobody had done it yet. It was a hole waiting to be filled. It's also really fun to embarrass the compiler!

A deep understanding of the algorithm took me a long time, I think a few months. That was mainly due a lack of information describing the technique. The paper that Andrei linked to (Granlund-Montgomery) is dense and contains a significant error, which I was never able to get resolved. Henry Warren's celebrated Hacker's Delight is more accessible, but is also more of a proof-of-correctness than a learning resource. So my intuitive understanding came from my own investigating and playing around, which is what lead me to find an improvement on the algorithm.

Implementing libdivide took me maybe six months of my hobby time. It's not just the core algorithm - there's a lot of auxiliary functions, for example to compute the high half of a 64 bit multiply in SSE. But working at that level is tons of fun.

Incidentally, I wrote up what I hope to be the most accessible (yet still rigorous) description of the algorithm at http://ridiculousfish.com/blog/posts/labor-of-division-episo... . I advise anyone interested in learning more to start there, instead of the Granlund paper.


There is one oft-forgotten case that neither compilers nor (seemingly) libdivide seem to handle: when we know that an integer is a multiple of some constant, and we just want to get the result of the exact division.

This case allows for a simpler method, exemplified here (it also works for signed integers, but more care is needed with the shifting): http://goo.gl/D5q9IO

EDIT: On second thought, compilers are also not doing their best job on that example. f1 could be simplified to

    mov  rax, 4865095698
    add  edi, 1
    imul rdi
    shrd rax, rdx, 39
    ret
which has a shorter critical path.


> when we know that an integer is a multiple of some constant

How is a compiler supposed to track that information? That requires some serious dependent typing.


It's not (by itself, at least). But you can give it hints, as in my example above (with __builtin_unreachable; other compilers have similar facilities, like __assume in MSVC). The compiler should, in theory, be able to deduce that x % 113 is always 0, and therefore must be a multiple of 113.


Oh God. I spent hours trying to understand how it works, when I saw it yesterday :/ I wish I had seen your link!


Just an FYI for anyone reading: It's not the multiplicative inverse of 10 modulo 2^64. Multiplying by it only works if the number is divisible by 10.

What it's happening here is something more clever:

  let z be: (2^67 + 2)/10. 
2^67 isn't divisible by 10 (that's why we add 2).

  z = 14757395258967641293L
Now, I claim that

  n/10 = floor(z * n/ 2^67)
z is an integer, but it represents the exact division of

  (2^67 + 2)/10
 
we distribute the n:

  (n * 2^67)/10 + 2 * n/10
we distribute the 2^67 division:

  (n * 2^67)/(10 * 2^67) + 2 * n / (10 * 2^67)
simplify the terms:

  n/10 + n/(10 * 2^66)  [1]
Now:

  floor(x/d + c) == floor(x/d) if c < 1/d
we want to take floor of that number[1], but as

  n/(10 * 2^66) < 1/10
(the maximum value for n is 2^64 - 1), it follows that

  floor(z * n / 2^67) = n/10.
That's what the code is doing: 0xcccccccccccccccd is 14757395258967641293L the multiplication of z * n fits in 128 bits, and the mul instruction stores the higher 64 bits of $rax * $rdx into $rdx.

We need to divide by 2^67, whichs is shift 3 67 bits to the right. If we take the higher part (rdx) we get the higher 64 bits, and then we shift 3 bits to the right (shr $0x3, %rax) and that's our answer :).

Cool trick.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: