5 ms·
> The cost of doing it in software is just a highly predictable (not taken) branch after every integer arithmetic operation that the compiler can't prove stays
by jcalvinowens 12y ago
> The cost of doing it in software is just a highly predictable (not taken) branch after every integer arithmetic operation that the compiler can't prove stays within bounds.
While I agree with you that hardware overflow traps are a bad idea, I think the article's author is referring more to the general overhead of a software BIGNUM implementation. Specifically with his JavaScript example, I think it's very plausible that using integers instead of IEEE754 would incur a 10% overhead if you were throwing lots of big enough numbers around.
What the author really wants is a hardware BIGNUM implementation, not overflow trapping. I just don't think he really thought it through.
His contention that simply checking for overflow in C or C++ incurs a 5% overhead is unquestionably false though. Typically one would check for integer overflow in C like this:
unsigned int do_addition(unsigned int a, unsigned int b)
{
unsigned int tmp;
tmp = a;
a += b;
if (a < tmp)
asm volatile ("nop;" ::); /* Handle it here */
return a;
}
Any decent compiler will do the right thing (GCC with -Os):
0000000000000000 <do_addition>:
0: 89 f0 mov %esi,%eax
2: 01 f8 add %edi,%eax
4: 73 01 jae 7 <do_addition+0x7>
6: 90 nop
7: c3 retq
I think rwallace is absolutely right in saying the overhead of such a check would be very close to zero in the non-overflow case.
- stormbrew 12y agoNote, in case someone blindly takes this as advice: This method of expressing it in C doesn't work for multiplication (you could wrap more than once). I'm not sure you can express an overflow check for multiplication purely in portable C (without library or intrinsic support), actually. Well, I guess you could break the multiplication into checked additions manually, but that's probably not a great idea.
- sounds 12y agoIn x86 assembly almost all multiplication operations return a result double the width of the inputs: http://www.aldeid.com/wiki/X86-assembly/Instructions/mul http://www.aldeid.com/wiki/X86-assembly/Instructions/mul Checking for overflow is just a matter of testing if the high-order bits are zero.
- stormbrew 12y agoYep, in assembly it's usually no question possible. That's why I specified portable C. C has some interesting gaps for something that's long been described as a macro for assembly. A rotate operator would also be nice.
- pbsd 12y agoThe correct thing to do is also to check the carry and overflow flags after (I)MUL, which does the right thing even when only the lower bits of the multiplication are saved. Here's an example where there is overflow, but your suggestion would not catch it: mov eax, 0x7fffffff mov ecx, 2 imul ecx ; edx:eax = eax * ecx ; edx = 0 ; eax = 0xfffffffe ; overflow flag = 1 There is signed overflow here, but testing edx (the higher word of the result) would not catch it.
- sounds 12y agoYes. I decided not to mention IMUL to keep the post simple. There are also issues with detecting overflow of SSE multiplies (signed and unsigned).
- jcalvinowens 12y ago> Note, in case someone blindly takes this as advice: This method of expressing it in C doesn't work for multiplication (you could wrap more than once). Yeah, you're absolutely right. > I'm not sure you can express an overflow check for multiplication purely in portable C (without library or intrinsic support), actually. Well, I guess you could break the multiplication into checked additions manually, but that's probably not a great idea. I've never tried to implement this, but couldn't you look at the position of the most significant bit in the multiplicands to guess if the multiply will overflow? You'd have to be okay with false positives I suppose? Naively it would be slow, but if you have a hardware CLZ instruction it could be pretty fast. I did find this on SO: x = a * b; if (a != 0 && x / a != b) { // overflow handling } That makes sense, but of course it would be HORRENDOUSLY expensive. If you're willing to not be non-portable, you could just do expanding multiplies and check the high word: struct exmulres { unsigned long hi; unsigned long lo; }; static inline __attribute__((always_inline)) struct exmulres do_one_expanding_multiply(unsigned long a, unsigned long b) { struct exmulres res; #if defined(__x86_64__) asm ("movq %0,%%rax; mulq %1; movq %%rdx,%1; movq %%rax,%0" : "=r" (res.lo), "=r" (res.hi) : "0" (a), "1" (b) : "%rax","%rdx"); #elif defined(__i386__) asm ("movl %0,%%eax; mull %1; movl %%edx,%1; movl %%eax,%0" : "=r" (res.lo), "=r" (res.hi) : "0" (a), "1" (b) : "%eax","%edx"); #elif defined(__arm__) asm ("umull %0,%1,%2,%3" : "=r" (res.lo), "=r" (res.hi) : "r" (a), "r" (b) :); #else #error "No expanding multiply assembly has been written for your architecture" #endif return res; } It probably wouldn't be very difficult to fill that out for all the CPU architectures Linux supports, which would cover all your bases unless you're doing super embedded stuff. EDIT: Typo
- stormbrew 12y agoYeah that div check works I guess, but as you say is horribly expensive. For the latter it looks like at least on recent versions of gcc, you can do something like (WARNING: Just a vague proof of concept, please don't use it. It might kill kittens): uint64_t func(uint64_t a, uint64_t b) { unsigned __int128 res = (unsigned __int128)a * (unsigned __int128)b; if (res >> 64) { asm volatile ("nop;" ::); /* handle here */ } return (uint64_t)res; } Which compiles to this in gcc -Os and -O3: 0: 48 89 f8 mov %rdi,%rax 3: 48 f7 e6 mul %rsi 6: 48 85 d2 test %rdx,%rdx 9: 74 01 je c <func+0xc> b: 90 nop c: f3 c3 repz retq Which I'm pretty sure could be optimized further by using the overflow flag, but the compiler doesn't seem to want to ignore the upper word. edit: and it probably suffers the same problem as described here: https://news.ycombinator.com/item?id=7848771 https://news.ycombinator.com/item?id=7848771 -- and also probably has general issues with signedness that would need to be further checked. edit2: changed it to uints to simplify and avoid a whole class of problem. I still don't think you should go use this, though. (inspiration from SO: http://stackoverflow.com/a/13187798 http://stackoverflow.com/a/13187798)
- xenonflash 12y agoHe also picked one of the most trivial cases - unsigned addition. Signed data, especially signed multiplication, requires a lot more steps. I'm working on a set of numerical problems in C now that involve checking for integer overflow. The best way of doing software overflow checks depends on the larger scope of the problem. You can knit your checks into various places in your code in ways that avoid unnecessary duplication of computational effort. However, that is a lot of work and has the potential for hidden programmer introduced errors. The real problem is that languages like C simply don't allow you to take advantage of hardware which already exists in the CPU without writing in-line assembler. A standard set of "checked" math macros which handled the portability issues would probably satisfy most C applications. Edit: For addition, subtraction, and multiplication, you just take one operand, calculate the largest possible second operand for that data type which won't overflow, and check that the actual second operand doesn't exceed it (remembering to take signs into account). For division and modulus, check for division by zero. For multiplication, division, modulus, negation, and absolute value of signed values, check that you are not negating the maximum negative integer, as integer ranges are not symmetrical (e.g. one byte is -128 to +127). If you are looping over arrays and have multiple checks for different cases (e.g. negative, positive, etc.), then you can have different loops for different cases and so avoid redundant checks for that data. I'm working on this sort of application, so the above works out best for that. If you're doing something a bit different, then different algorithms may make sense. Unfortunately, there's not universal one-size-fits-all solution to this problem in software.
- stormbrew 12y agoIt's surprising there aren't at least intrinsics for some of these things in GCC/clang. At least as far as I can tell.
- Someone 12y agohttp://clang.llvm.org/docs/LanguageExtensions.html#builtin-functions http://clang.llvm.org/docs/LanguageExtensions.html#builtin-f..., the search for "Checked Arithmetic Builtins"
- Someone 12y agoA smarter way is to split both numbers to multiply into two halves, and pretend to do two-word arithmetic on a machine with words half the size. In decimal, that means that you compute the product of two-digit numbers using single-digit multiplications and shifts (= multiplications by 10): 23 x 67 = (20+3) x (60+7) = (2x3) x 100 + (2x6) x 10 + (3x7) x 10 + 3x7
- maxlybbert 12y agoThere are better checks. I'm generally not a fan of Microsoft, but the SafeInt library ( https://safeint.codeplex.com/ https://safeint.codeplex.com/ ) does those kinds of checks in sofware. I don't remember where I read it, but regarding multiplication: if you multiply two N-bit numbers together, the result will always fit in a 2N-bit number. For instance, the product of two 16 bit numbers will fit in 32 bits, the product of two 32 bit numbers will fit in 64 bits, etc. You can assign the product to an integer able to hold the larger size and then check if it would also fit in an integer of the smaller size.
- cliffbean 12y agoThere probably is close to zero overhead in a small benchmark. Hardware branch prediction is indeed good. However, it's a finite resource. In a large application, lots of needless branches everywhere translates into fewer branch prediction resources available for the branches that matter, which means more mispredictions. Also, they take up icache, itlb, etc. It's not at all obvious that the overhead would be close to zero in context.
- tomp 12y agoWould it be possible to mark the JO instruction (jump-if-overflow) as unlikely, so that the CPU would always predict the branch to not be taken, without consuming one branch prediction slot?
- jcalvinowens 12y ago> so that the CPU would always predict the branch to not be taken AFAIK there isn't any notion of likely/unlikely in object code on most CPU architectures. I'm only really familiar with x86 and ARM though. __builtin_expect() in GCC (the "likely()" and "unlikely()" macros in the Linux kernel use this) can do a lot of things to optimize branches, like prefetch instructions and decide whether or not the jump should be the usually taken or usually not taken side, but it can't actually emit instructions that directly tell the CPU "always predict this" AFAIK.
- jcalvinowens 12y ago> It's not at all obvious that the overhead would be close to zero in context. That's a fair point, although I think using a single function for all your checked addition like the one above would go a long way towards mitigating the resource waste you mention. Maybe that's a naive assumption on my part: I suppose you could construct a branch predictor that maintains state based on the function call chain as opposed simply using the address of the branch... but that seems like it would be prohibitively complex.