Hacker News new | ask | show | jobs
by ReleaseCandidat 971 days ago
What everybody should read before using these (Aaron Ballman is a Senior Staff Compiler Engineer for Intel and is the lead maintainer of the Clang open source compiler): https://blog.aaronballman.com/2020/08/dont-use-the-likely-or...
4 comments

It's an odd article. The basic thesis is "use pgo instead", which is reasonable enough. It starts with a long diversion through edge cases of the attributes, none of which seemed particularly impactful in practice. Perhaps he was worried that just recommending pgo on its own wouldn't convince many people. There are many situations where you can't enable pgo organizationally, for example if you're part of a large company with a centralized build system, or if you package a library meant to be inlined, and want your code to be optimized even when it's built without pgo. The comparison to `register` and `inline` are interesting, but not very useful imo. Whether a given variable will benefit from being put in a register, or a function from being inlined, is usually very local information. The compiler can see when the variable will be accessed down the road, and hence whether moving it to the stack will tend to slow down later code. Whether a branch is likely or unlikely will frequently depend on information the compiler doesn't have (sans pgo), such as the distribution of an argument variable. In fact, it seems like this very information would be useful to a compiler in determining if it should inline a function or keep a variable in a register.
PGO also doesn't always optimize in the correct direction. If I have an error handling path in a hot loop, PGO can only optimize around its branch if it actually sees that error occur, and then it will draw wrong conclusions about the branch into the error handler because, absent fudging the tests, it will think the error path has higher importance than it does. I don't want the compiler to optimize for the error path at all, I want it to pessimize it to prioritize the non-error path. But the PGO analysis doesn't know that, it only sees branch patterns and probabilities, and not all error handling paths use exceptions.

PGO is also a pain to use in some situations. You need to be able to regularly exercise all of the main paths in the program under instrumentation, preferably automated, using a configuration as close to release build as possible. That's hard to do when your release build lacks automation support, has nondeterministic behavior by design, cross-compiles to another platform, or requires networked services to exercise main paths. I don't even know how people deal with PGO when there is a requirement for deterministic builds.

> Whether a branch is likely or unlikely will frequently depend on information the compiler doesn't have (sans pgo),

Exactly! Often it is simply impossible for the compiler to know. In this respect it is similar to std::unreachable.

I think that everybody should instead read proposal that introduced this feature, P0479R2[1]

[1] https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p04...

and look at code in the wild

[2] https://github.com/search?q=[[likely]]+language%3Ac%2B%2B&ty...

I loved it! Thanks! The last part resumes my thinking: These attributes are starting to look a bit more like some other code constructs we’ve seen in the past: the register keyword as an optimization hint to put things in registers and the inline keyword as an optimization hint to inline function bodies into the call site. Using register or inline for these purposes is often strongly discouraged because experience has shown … My take is: 99.9% of the time, when you start shaving some CPU cycles here and there, instead of doing algorithmic optimization, something is going wrong.
This is a pretty coherent argument that that new feature is broken and best ignored. Hopefully that's the approach clang will take.

PGO is a mixed blessing and detracts a bit from the thrust of the article. The more obvious conclusion is to continue using builtin_expect (on the boolean guard of a branch) which works great and has done for ages.

That is also covered by new C++ attributes, namely [[assume(expr)]].

However better not give data to the function that contradicts the condition if you don't want to figth nasal daemons.

Glad assume(expr) is available, if it maps onto builtin-expect but confused by the UB reference. Why would taking the less likely path be undefined behaviour?

Edit. It's because assume does not map to expect, it maps to builtin_assume. So that's just another way to write undefined c++.

Ah, I thought it was the same, I spend most of my C++ coding time on VC++ anyway.