3 ms·
This shows that C and C++ are getting more and more out of sync with modern CPUs. Outdated if you want. Modern CPUs have wide multiplies (32x32->64), wide div
by zvrba 14y ago
This shows that C and C++ are getting more and more out of sync with modern CPUs. Outdated if you want.
Modern CPUs have wide multiplies (32x32->64), wide divides (64/32->32), a whole bunch of useful arithmetic status flags, yet C gives you access to nothing of that. Instead, it forces you to divide first (very slow) before multiplying to ensure that overflow won't happen.
Even a simple division check before multiplying isn't sufficient, since there's a border case where the two operands are INT_MIN and -1. In 2nd complement arithmetic, both division and multiplication will overflow. So you need yet another check.
But gcc folks are also obtuse: an implementation is allowed to define what is technically UB. Optimizing out operations that are perfectly defined on a 2nd-complement architecture seems to be against the "spirit" of C -- that you can guess the compiler's output with reasonable certainty. I'd call this outright maliciousness on the part of compiler implementors.
People dislike C++ saying too much is happening behind the curtains. But examples like this show that C is not much better either.
- colanderman 14y agoDude, you don't know what you're talking about. Compile this for 32-bit x86 (gcc -m32 -O3 -S): uint64_t foo(uint64_t x, uint64_t y) { return (uint64_t) x * (uint64_t) y; } Big, ugly, as expected: 3 multiplies and 2 adds. Now compile this: uint64_t foo(uint32_t x, uint32_t y) { return (uint64_t) x * (uint64_t) y; } One instruction (mull). GCC totally knows about 32x32 -> 64 multiplies; you just don't know how to use them.
- zvrba 14y agoDude, YOU have no idea what you're talking about. I'm talking about C, the language, not what one particular implementation gives you. AFAIK, C11 doesn't have a 128-bit int type, so how do you get the full result of 64x64 bit multiply? How do you access the carry flag? The overflow flag? Etc.
- colanderman 14y agoThe C language doesn't, and has never, said anything about what the assembled code should look like. If you want performance, you have to use a good compiler. That's true with any language. You're right, C11 doesn't have a 128-bit integer type; that should be remedied. Fortunately all major compilers (GCC, LLVM, VC++, and ICC) provide one. Carry: "if (x + y < x)" compiles to code that uses the carry flag. Overflow: "if ((int32_t) (x + y) < 0)" compiles to code that uses the overflow flag. And the best part is, code written like this is portable to architectures such as TileGX that don't have status flags.
- zvrba 14y ago> Carry: "if (x + y < x)" compiles to code that uses the carry flag. If x and y are signed, you're relying on UB, which the compiler might optimize out. > "if ((int32_t) (x + y) < 0)" compiles to code that uses the overflow flag. Again, which type are x and y? If signed, this code is UB. If unsigned, then the result of conversion to int32 may not fit into int32, in which case the result is implementation-defined, or and implementation-defined signal is raised. Portable? Right.
- zvrba 14y ago> Carry: "if (x + y < x)" compiles to code that uses the carry flag. If x and y are signed, you're relying on UB, which the compiler might optimize out. > "if ((int32_t) (x + y) < 0)" compiles to code that uses the overflow flag. Again, which type are x and y? If signed, this code is UB. If unsigned, then the result of conversion to int32 may not fit into int32, in which case the result is implementation-defined, or and implementation-defined signal is raised. Portable? Right.