3 ms·
Oh yeah, I love this stuff. On my Linux box, clang 3.4 is 50% slower than gcc 4.9 on -O3 on that code, for all inputs. The key difference is, unsurprisingly,
by ridiculous_fish 12y ago
Oh yeah, I love this stuff.
On my Linux box, clang 3.4 is 50% slower than gcc 4.9 on -O3 on that code, for all inputs.
The key difference is, unsurprisingly, in the inner block: for (int j = 0; j < 50000; j++)...
clang's inner block asm: https://gist.github.com/ridiculousfish/24b16ba5b4e8abbe051b https://gist.github.com/ridiculousfish/24b16ba5b4e8abbe051b
gcc's inner block asm: https://gist.github.com/ridiculousfish/bee5217d6ae1f4f34a5d https://gist.github.com/ridiculousfish/bee5217d6ae1f4f34a5d
Notice that clang's is much longer. Also notice all of the "# imm = 0xFFFFFFFFFFFFFC00" lines in clang. Those immediate values correspond to inlining the norm() functions: it's how clang is computing x%1024.
How do you compute x%1024? It sure would be nice to strength-reduce it to x&1023, but that's always going to produce a non-negative number, which is not what you want if x is negative. That is, -1025 % 1024 ought to be -1, and not 1023. So that strength reduction (x%1024 -> x&1023) is only valid if you can prove that x is non-negative.
And that's the first big difference. gcc-4.8 convinces itself that all of the ints involved are non-negative, and so it computes x%1024 as x&1023. clang fails to notice that all ints are non-negative, and so emits the slow path for the mod.
Let's replace int with unsigned int, and see what happens:
clang's uint asm: https://gist.github.com/ridiculousfish/c35ffcff5963160c9622 https://gist.github.com/ridiculousfish/c35ffcff5963160c9622
This allows the x&1023 strength reduction, but now we hit the second big difference: clang is futzing with xmm registers, perhaps packing a single 'vec' into a register. The codegen just looks silly to me - all those pshufd and punpckldq. Let's disable that noise with -mno-sse:
clang's uint asm (no SSE): https://gist.github.com/ridiculousfish/0afc79d458ae30dc6327 https://gist.github.com/ridiculousfish/0afc79d458ae30dc6327
Now clang is totally kicking ass, with fewer instructions than gcc, and about 17% faster. (Of course gcc may in turn be massaged to be even faster yet.)
Timings on my i3 NUC with -O3, input 4. Lower is better:
gcc w/ signed: 4.78 sec
clang w/ signed: 7.11 sec
gcc w/ unsigned: 3.83 sec
clang w/ unsigned: 7.11 sec
clang w/ unsigned no-sse: 3.14 sec
In summary, clang is missing a big optimization, and then emitting some dubious SSE code. Fixing those more than doubles its speed. This illustrates the importance of monitoring your codegen for tight loops, and the hazards of micro benchmarks!
- detrino 12y agoI think most of the SSE slowness is because the weird static vec::add that uses pass by value, try changing that to pass by reference.
- ridiculous_fish 12y agoWow, you're right! That improves the time from 7.11 to 3.94 seconds. I was skeptical because 'add' is inlined, which I expected would eliminate pointless copies, but I guess not!