r/linux Jun 25 '26

Development One Line x86 Change To GCC Compiler Nets +12% Benchmark Win For Modern Intel/AMD CPUs

https://www.phoronix.com/news/GCC-x86-Generic-Mispredict
Upvotes

42 comments sorted by

u/anh0516 Jun 25 '26

Will be interesting to see how and if this negatively affects older CPUs, and how old they have to be in order to be negatively affected by this.

u/mitch_feaster Jun 25 '26

> if this negatively affects older CPUs

Or other benchmarks. I'd be very surprised if this doesn't cause regressions in other benchmarks/use cases. The patch only mentions a single benchmark (544.nab_r). Seems like bad engineering but I'm also totally ignorant as to the development process for gcc, maybe this is normal?

u/Albos_Mum Jun 25 '26

From what I can tell it's mostly updating the compiler hints to better suit how modern CPUs have changed over the years, I'd wager it'll negatively affect a number of older CPU designs but forcing gcc to compile with specific, more-modern architectures in mind is a big part of where a handful of distros (eg. CachyOS, Clear Linux) maintained a noticable performance advantage.

Gotta remember, the generic x86_64 optimisations would have been originally designed for the Athlon64 and changes such as this have been fairly limited and/or conservative in the past for the most part, instead relying on people using specific compiler flags such as march=native or similar to deviate from that with interest in providing updated optimisation hints only becoming more of a priority recently once it became clear that there was some reasonable performance gains being left on the table.

u/nothingtoseehr Jun 25 '26

When compilers are choosing instructions to implement a functionally identical implementation, they take many parameters into question: the state of the current function, the code preceding it, how much is left etc etc. One of these values is "execution cost", basically a generic unit that measures how much choosing an instruction "costs" in terms of execution speed

GCC almost always leaves generic defaults for generic targets, because well, it makes sense. If you don't know your target, it's better to play it safe than to risk an optimization causing slowdowns even if it produces suboptimal code. They're not necessarily linked to any architecture, many generic profiles for a lot of architectures use the default costs. IIRC on GCC generic_cost targets Haswell and later

The code changed here basically increases the "cost" of the compiler choosing a branching instruction (not really a specific instruction, but I'm not too sure how GCC's backend works). It's true that modern CPUs have big pipelines, but branchless conditional instructions have always been quite slow on x86/64, a gift from it's CISC legacy.

u/braaaaaaainworms Jun 25 '26 edited Jun 25 '26

They would have to have shorter pipelines(for smaller branch misprediction penalty) and low instruction fetch bandwidth or slow instruction decoder to make one (if ... endif) or two (if ... else ... endif) branches cheaper than executing a few more instructions and I don't think any recent microarchitecture would perform worse from this, in fact, stuff like pentium 4 or bulldozer might benefit from even bigger misprediction penalty. I'd be worried about smaller cores like crestmont

u/[deleted] Jun 25 '26

[removed] — view removed comment

u/Kevin_Kofler Jun 25 '26

This is just tuning the costs which tell the compiler to prefer one instruction over another, equivalent one. This is unlikely to have any security implications.

u/Kevin_Kofler Jun 25 '26

If anything, reducing the number of potential branch mispredictions (by increasing their cost in the GCC cost model as this commit does) will reduce the potential for SPECTRE-like vulnerabilites.

u/cake-day-on-feb-29 Jun 26 '26

I get what you're trying to say, but no, the number of branch mispredictions for a program does not change how many vulnerabilities exist to exploit branch predictors.

u/thefeedling Jun 25 '26

I’d love to see a comparison with latest clang

u/DerekB52 Jun 25 '26

I've read 2 whole books and parts of a few others on building programming languages. I've built a toy interpreter, a just in time compiler, and a full compiler.(All toy level of course). In my day job I helped build a web framework.

I've done low level programming. I know some stuff. This article is like 3 paragraphs and it is like I've entered a new field or something. I know absolutely nothing about branch predictions, or how they can go wrong. Implementing guess work in my compiler sounds crazy.

TIL my compiler is guessing, and wasting 12% of its time guessing a little too loosely.

u/intersectRaven Jun 25 '26

It's not that the compiler is guessing. It's more like, as newer CPUs are made, they've gotten more and more expensive when something "unlikely" occurs. If you're interested, you can try looking up architecture in depth analysis whenever a new CPU architecture is released (Zen, Nehalem, etc). I recommend Anandtech's analysis but I don't know if it's still somewhere on the internet archives.

u/[deleted] Jun 25 '26

[deleted]

u/Krutonium Jun 25 '26

I will forever be sad we never saw the Pentium V, which would have actually hit the oft promised 6-10GHz range (Samples did!), but with an Absurdly long pipeline and 200+W of power draw.

Honestly for the right workload that would have been fire.

u/cp5184 Jun 25 '26

There was a superfast IBM power architecture, ~6-7GHz iirc. Power6 I guess. 5GHz commercial with 6GHz prototypes apparently or something. It was a lot more impressive in like 2005.

But high frequencies aren't that amazing... "Just one more ghz! Just one more, one last ghz I promise" doesn't take you as far as you might think.

u/Krutonium Jun 25 '26

To be clear, I'm just saying it'd have been fire, not that it'd have been good. lmao

u/alvenestthol Jun 25 '26

Shorter pipelines, but more of them - instead of a single 30-stage pipeline, the CPU is doing 10 pipelines that are exactly as long as they need to be, so it'll still only stall for something like 5 cycles per branch miss, but it's also throwing away some 30 other instructions at various points of their pipelines which would've all completed in 10 cycles

(numbers are arbitrary)

u/Albos_Mum Jun 25 '26

Both Intel and AMD have figured out various methods to allow for similar pipeline lengths while avoiding most of the pitfalls of the Netburst or Bulldozer style architectures, while improved manufacturing, clever tech like Turbo Boost and a slower ramp up of clock speeds has helped ensure we can have the high clock speeds without the high TDPs/power consumption.

u/anh0516 Jun 25 '26

It's not the compiler guessing, but the CPU itself, through branch prediction.

Considering how expensive a missed prediction is allows the compiler to optimize the code better and increase the hit rate.

u/pigeon768 Jun 25 '26

So let's say you have some code:

int function(int x, const int* numbers, int n) {
  int count = 0;
  for(int i = 0; i < n; i++)
    if (x > numbers[i])
      count++;
  return count;
}

There are two ways the compilers can turn that into assembly/machine code.

int foo(int x, const int* numbers, int n) {
  int count = 0;
  for (int i = 0; i < n; i++) {
    if (x <= numbers[i])
      goto skip;
    count++;
  skip:
  }
  return count;
}

or:

int bar(int x, const int* numbers, int n) {
  int count = 0;
  for (int i = 0; i < n; i++) {
    int greater_than = x > numbers[i];
    count += greater_than;
  }
  return count;
}

foo() will be fast if the CPU is usually able to predict whether the branch happens. For instance, if the list is sorted, or if x is very close to the bounds of the list. However, if it's often wrong, it will be slow, because the CPU has to backtrack and do work over again. bar() will always run at the same speed. Converting the flag into an integer does not depend on the actual data, and so the CPU can proceed with its other work and go to the next iteration and never has to backtrack. However it will always have to do the addition.

So the compiler has to make a choice: do branchy code, and sometimes be fast and sometimes be slow, or do branch free code, and always be medium speed? Sometimes the compiler has information that would indicate that it the CPU will usually be able to predict the branch, nudging the compiler towards the branchy version. Sometimes the work involved in doing all of the work every time is a lot of instructions, nudging the compilers towards the branch free version.

This change gives the compiler a nudge towards doing branch free code.

See also the most upvoted post of all time on Stack Overflow: https://stackoverflow.com/questions/11227809/why-is-conditional-processing-of-a-sorted-array-faster-than-of-an-unsorted-array

u/x0wl Jun 25 '26

It's the CPU that is guessing, specifically it's guessing where a branch will go before it's computed, and guessing incorrectly is expensive and slow.

u/SmileyBMM Jun 25 '26

The genius techniques modern computer components use to get more performance never ceases to amaze me. It's truly humbling.

u/MatchingTurret Jun 25 '26

Implementing guess work in my compiler sounds crazy.

That's why there is profile-guided optimization. Reduces the guessing. You can also give the compiler a hint with __builtin_expect.

u/crashtua Jun 25 '26

Treat x86 asm as a very high level language, because behind it whole machinery happens xD

u/hxka Jun 26 '26

It's not a very high level language. It is however an interface that the CPU implements rather than the description of its architecture.

u/oursland Jun 25 '26

Check out Agner Fog's software optimization resources. For details on branch prediction, see book 3: " The microarchitecture of Intel, AMD and VIA CPUs: An optimization guide for assembly programmers and compiler makers".

u/OozeOutOfMyMoose Jun 26 '26

If you want a deep dive, read Denis Bakhvalov's performance book, solve the assignments, you will not be disappointed

u/cake-day-on-feb-29 Jun 26 '26

web framework... I know absolutely nothing about branch predictions

Another day, another instance of web devs showing just how little they care for performance and efficiency.

TIL my compiler is guessing, and wasting 12% of its time guessing a little too loosely.

Even though you still don't understand what's going on, it still doesn't matter, it's not going to change the 20 seconds it takes for node to load all 700MB of untyped crappy JavaScript. In this economy, I'd be scared of users revolting against webdevs. Who wants to dedicate 1.5GB to their note-taking app? That's $30 fucking dollars of RAM! Then every fucking "modern" webpage needs at least 500MB of RAM, and then every god damn app is yet another electron app that requires $20-$30 of RAM.

u/dlg Jun 25 '26

This update broke my workflow.

https://xkcd.com/1172/

u/daemonfly Jun 25 '26

"This is for those just relying on the generic x86/x86_64 tuning and not any CPU-specific -march=native type builds."

So, probably won't affect my Gentoo build then, as my settings should be better than generic compiling with this. Should help more mainstream distros though.

I'd be interested in the gains on generic gaming distros like Bazzite. Many games already run better on linux, even if you're running the Windows build in wine/proton/etc...

u/zissue Jun 26 '26

That was the line that ruined it for me too. I was thinking "oh this is cool!" Then I saw that it is only for generic x86{,_64} and not those of us who compile with something like -march=native. :(

u/wintrmt3 Jun 25 '26

In a single microbenchmark, usual moronix.

u/TampaPowers Jun 26 '26

OP is a serial poster of their stuff, looks to really go for getting that million link karma.

u/Ahmouse Jun 26 '26

*happy Gentoo compiling noises*

u/1Heineken Jul 05 '26

can we impliment this code on personal computers or does it have to be implimented by devs ?

u/ballistua Jul 08 '26

I wonder what Linus Torvalds compiles with, I know he doesn't like gcc

u/newsflashjackass Jun 25 '26

Intuitively, I feel this is a backdoor or it closes one.

That is not an exclusive "or".

u/wintrmt3 Jun 25 '26

It just changes a price value for a heuristic, why the insane dramatics?

u/newsflashjackass Jun 25 '26

Perhaps the parser is configured for insane / dramatic parsing?

u/nothingtoseehr Jun 25 '26

It's literally just a macro

/* We assume COSTS_N_INSNS is defined as (N)*4 and an addition is 2 bytes. */

#define COSTS_N_BYTES(N) ((N) * 2)

u/newsflashjackass Jun 25 '26

Correct. It also replaced "just a macro" so by that measure no event was observed.

I am unable to distinguish your rationale from the one that rates software based on its "lines of code".