Bubble sort slower with -O3 than -O2 with GCC
stackoverflow.com
stackoverflow.com
Was it ever reported?
I did a quick search on https://gcc.gnu.org/bugzilla for any issues, of any status, tagged with missed-optimization and containing the term "store-forwarding" or "bubble sort" and didn't see it (maybe I'm not searching right?)
If the code slows down, it's usually because the compiler has generated a bunch of code because it doesn't know what hot path to schedule for.
They will generate hundreds to thousands of instructions for factorial if you let them because it turns it into a loop, then vectorizes the loop.
Undefined Behaviour pub quiz question in there ^^
[1]: https://en.m.wikipedia.org/wiki/Profile-guided_optimization
Edit: It is possible that I just don't understand how to actually implement PGO
gcc -fprofile-generate ...
./a.out ...
gcc -fprofile-use ...
I don't know the current state of https://gcc.gnu.org/wiki/AutoFDO-O2 -g: 1.07s
-O3 -g: 1.17s
-O3 -g -fprofile-generate/use: 0.84s
(maqao cqa from https://maqao.org would give comprehensive info on the generated code.)
--
/* Tom, don't let me ever catch you using bubblesort again! -- Don */
— Knuth in dvips/afm2tfm.cclang -O3 -march=native: 0.769s
clang -O3 : 0.830s
clang -O2 -march=native : 0.904s
gcc -O2 -march=native :1.007s
gcc -Os -march=native : 1.148s
gcc -O3 -march=native : 1.928s
gcc (GCC) 11.1.0
clang version 12.0.1
On Ryzen 3700x
For example I compared an insertion sort and see the same performance between O2 and O3:
void insertionsort(int *buf)
{
for (int i = 1; i < n; ++i) {
const int x = buf[i];
int j = i - 1;
for (; j >= 0 && buf[j] > x; --j) buf[j + 1] = buf[j];
buf[j + 1] = x;
}
}
bubble O2: 1.208sbubble O3: 1.469s
insertion O2: 0.126s
insertion O3: 0.126s
I can't remember -O3 ever being faster than -O2, it's usually slightly slower for me.
In the past, at a time when I worked on a very performance sensitive codebase that was also limited in scope, we compiled with -Osize and did all the loop optimizations we wanted manually (and with pragmas). That produced faster code than -O2 or -O3.
A more granular control over optimisation would be good, however.
In other words, loop unrolling is, more often than not, harmful.
I turned on PGO for the D compiler recently and it compiled it's tests 15% faster. Boom.
Notice that v3 is no better and may even be a hair slower.
If for some reason you want O2 but with vectorisation you can enable it, and if you want O3 without inlining or loop unrolling (the same thing), you’re welcome to specify that.
...and then there's Atom, which is also an odd one - I believe it's more like a classic Pentium (P54).
Certainly on earlier microarchitectures it did help a lot more than it does now, and there were some nice wins on the PIII for it
Arguments around code size have been largely irrelevant for quite some time, so long as performance increases.
If I put on a tinfoil hat, I'd suspect Intel was at least an influence. They're the ones adding AVX, AVX2, 512 and providing their own compiler to take advantage of them, while crippling AMD parts. That is until they switched to LLVM.
Code size matters less than it once did, now that we have bigger and smarter caches. But bigger, while sometimes big enough, is never big.
my results:
time ./size-optimized 30000 real 0m1,164s
time ./optimized2 30000 real 0m1,207s
time ./optimized3 30000 real 0m1,216s
This is not actually surprising: the reliable operations are placed in 2, and the dodgy ones in 3. It is possible, even common, for 3 to be faster, but you can't rely on it. You have to measure.