Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

GCC generates code that's smaller but it isn't optimal, because of the potential for partial register stalls (and just overall register renaming issues)

It's of course not wrong, but using AH when you're dealing with RAX is a weird anachronism

Clang does the obvious, correct thing.



"smaller but not optimal" is not really true. It depends on what you're optimising for. In my experience, optimising for size overall, and then speed in the really performance-critical parts (with some expected expansion), gives the best results. Even the non-performance-critical code will have a noticeable effect if its larger size causes more cache misses.

Making use of the "partial registers" (I see them more as separate smaller registers that can be grouped together) effectively can avoid many more instructions.


I don't disagree with you, but then use EAX, not AH (which would also produce a smaller code as you don't need to use a 64-bit constant)

Optimizing for size is good, but what GCC did there made sense in the 32-bit days, but not that much today

Some code snippets use AH/AL as 2 separate registers hence the processor might rename them to different internal registers. But then when reading EAX the processor needs to update EAX accordingly as well.


TFA says clang actually uses an encoding of AND which operates on RAX but takes only 32b of constant so it is pretty much the solution you propose. And BTW, operating directly on EAX itself fucks up the upper half of RAX.


AFAIK it is standard for all 64-bit instructions that use immediates.


Interesting, it seems you are right and no 64b immediates are possible at all except in MOV.


AH/BH/CH/DH are so seldom useful that it's not really worth adding support for using them to the compiler. They only exist for 4 of the 16 registers, and they only let you address one random byte of the 8 bytes in that register. You get a bit slower code and a more complicated register allocator in order to save, what, a few bytes in the entire program?


AH is no more of an anachronism in 64-bit code than it was in 32-bit code; AL, AH, AX, EAX, RAX; all bit slices of the same register. The addition of RAX doesn't change the fact that AH is still a bit weird (it's the only one that doesn't include the low bits of the total register).


Do you know for a fact that Clang's code is faster here, i.e., have you measured it on actual hardware? Armchair performance estimation about "the potential" for something is often wrong...


In a micro benchmark, using the clang version appears to be a bit faster. However, note that code size can also become a relevant concern in larger code bases: Apple, for example, is said to generally build its OS code with -Os rather than -O2 or -O3 (and had -Oz as an even more aggressive custom code size optimization added to gcc back when they still were using it instead of clang).


It might not, but the GCC code has a potential issue in how it does things https://stackoverflow.com/a/41574531 (curiously the question is about GCC not doing it, apparently not always)


It's a performance trade off. It's not really surprising that GCC would make different performance trade offs when optimizing for different microarchitectures. (let alone most likely across different versions of GCC)

Why would you expect GCC to optimize for Pentium 4 (Netburst) in this day and age? (Especially given that the article is talking about Skylake.)


I don't expect it to optimize to P4, are you talking about this? (it's one of the answers to that answer)

> The quoted delay of 5 - 6 clocks is much better on later microarchitectures. For example from Sandy Bridge and Ivy Bridge, The Ivy Bridge inserts an extra μop only in the case where a high 8-bit register (AH, BH, CH, DH) has been modified

And you see that even on later architectures the point of avoiding AH makes sense (which is the opposite of what that GCC code does)


It's not obvious. If it was then GCC would not do it. Please don't use the word 'obvious' when it is to you but not to the majority of population.


It is obvious, though I meant "obvious and correct" not "obviously correct"


Have you looked at the actual GCC codebase? It's very easy to say something is obvious when you're looking at a problem which someone else has nicely isolated; it's much harder to dive into a complex codebase which has a very wide support matrix and say it's worth the effort to change working code instead of so many other things.

More bluntly, before now wouldn't most people have said it was “obvious” that Intel would support their own documented features?


The code produced by Clang is a direct translation of the C code. That's the obvious part of it

Most people here are not familiar with x86 assembly and its caveats it seems. Reading the Intel and AMD optimization manuals might be a good start

(and yes, the bug is not in GCC it's on the Intel processor)


> Most people here are not familiar with x86 assembly and its caveats it seems. Reading the Intel and AMD optimization manuals might be a good start.

Please don't make unsupported assertions that everyone but you is speaking out ignorance. It doesn't add anything to the conversation, especially when dealing with older codebases unless you can prove that this is and never has been the correct way to write that code. Otherwise it's just another way to say “CPU optimizations change over time and an open-source project doesn't have a team of experts tracking microbenchmarks to decide when to switch”.


Should be easy to prove me wrong if it's as easy as you say, I don't see why you're so annoyed by it

> “CPU optimizations change over time and an open-source project doesn't have a team of experts tracking microbenchmarks to decide when to switch”

Yes, that's what's happening, but the problem comes from the P6 architecture (though Netburst doesn't have those problems), this problem has been known for around 20 years


I suppose the choice of word was to describe what code a human would intuitively/idomatically write for this situation. Whereas compiler passes are often wont to sum up to unintuitive/non-obvious code that is "correct" in that it's a valid lowering of the higher level code but not likely what a human would've written. This can sometimes be a pro or a con in terms of the resulting code's performance.


Who can clarify? Google says: Qbvious - easily perceived or understood; clear, self-evident, or apparent.


The confusion is between "the assembly clang generates is the obviously correct assembly for this operation" (true) and "the obviously correct behaviour for a compiler is to generate the assembly that clang does" (disputable, as while the assembly GCC generates is less clear, there may be advantages to doing it that way in e.g. performance). "Clang does the obvious, correct thing." is ambiguous between these two meanings.


I disagree with "ambiguous", you sneakily changed "obvious" to "obviously" in the second sentence.

  obvious, correct != obviously correct


Sounds like by "obvious" the parent means that Clang uses the most trivial approach.


> Clang does the obvious, correct thing.

Can we all stop shitting on GCC all the time? Thanks.




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: