Discussion summary
Discussions highlight skepticism about modern C++ compilers' efficiency and complexity, with some advocating for developer control and understanding.
What the discussion says
- Some believe compilers underutilize CPU opcodes and are too complex.
- Others emphasize understanding hardware and code optimization over blind trust in compilers.
- Concerns about performance issues like debug build speed and build bloat are raised.
- Proposals for better exception handling and safer coding practices are discussed.
“Trust your compiler, but your code is only fast if you're lucky.”
“Modern compilers don't use a third of CPU opcodes, and their complexity is hard to grasp.”
Comments
Hacker News
You want to see a beautiful compiler? Look at Plan 9’s compiler suite. A man could understand and even build on that.
by Glandalf
by bluGill
That’s not necessarily an indication of the weakness of compilers. It also could be an indication that hardware designers could leave out instructions.
X86, in particular, will have lots of them for backwards compatibility reasons (extreme example: the old 80-bit x87 FP stack)
There also are instructions that are expected to never get used by ‘normal’ compilers but cannot be removed because they only make sense in lower-level code such as those for switching between protection levels, implementing compare-and-swap, etc.
by Someone
There are proposals to introduce better exceptions into C++. Like this: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p07....
But until it's not in the standard, people should use std::expceted instead.
by Panzerschrek
> std::visit over std::variant<A, B, C> is lowered to a switch over the active alternative.
> In this case, layout is probably doing more work than the dispatch mechanism itself.
Very likely because last time I checked visit lowers to a virtual call.
by mike_hock
by mwkaufma
by kzrdude
TL;DR:
A vast majority of the programmers I've worked with don't understand the nuances of FP in general, nor the various extents of IEEE-754 support in different programming languages.
So for important numerical programming, I think clarity regarding the FP operations being performed can be crucial. I'm just unclear if modern C++ is a significant factor for that.
by CoastalCoder
https://doc.rust-lang.org/std/primitive.f32.html#algebraic-o...
> Algebraic operators of the form a.algebraic_*(b) allow the compiler to optimize floating point operations using all the usual algebraic properties of real numbers – despite the fact that those properties do not hold on floating point numbers. This can give a great performance boost since it may unlock vectorization.
> The exact set of optimizations is unspecified but typically allows combining operations, rearranging series of operations based on mathematical properties, converting between division and reciprocal multiplication, and disregarding the sign of zero. This means that the results of elementary operations may have undefined precision, and “non-mathematical” values such as NaN, +/-Inf, or -0.0 may behave in unexpected ways, but these operations will never cause undefined behavior.
> Because of the unpredictable nature of compiler optimizations, the same inputs may produce different results even within a single program run. Unsafe code must not rely on any property of the return value for soundness. However, implementations will generally do their best to pick a reasonable tradeoff between performance and accuracy of the result.
by CodesInChaos
by chrka
as you've pointed out, you've literally micro-optimised this - isn't this what you'd expect? :)
by foxhill
https://github.com/protocolbuffers/protobuf/commit/9f29f02a3...
by charleslmunger
inline double algorithm_call(std::span<double const> xs) noexcept {
return std::accumulate(
xs.begin(),
xs.end(),
0.0,
[](double acc, double volts) {
auto mv = calibrated_mv(volts);
auto err = residual(mv);
return weighted_square(err) + acc;
});
}
more readable, concise, and easier on my eyes than inline double raw_loop(std::span<double const> xs) noexcept {
double sum = 0.0;
for (double volts : xs) {
auto mv = calibrated_mv(volts);
auto err = residual(mv);
sum += weighted_square(err);
}
return sum;
}
Sure, there are some algorithms in <algorithms> that I'm rather not reimplement myself, but this one is not it.by Joker_vD
That's what abstraction is about.
by fooker
by rzzzt
inline double ranges_pipeline(std::span<double const> xs) noexcept { auto costs = xs | std::views::transform(calibrated_mv) | std::views::transform(residual) | std::views::transform(weighted_square);
return std::ranges::fold_left(costs, 0.0, std::plus<double>{});
}It's still a bit verbose, because C++ doesn't allow universal function call syntax. It will be even more concise in other languages like D.
by Erlangen
Join the discussion
Write your take first — we'll ask for email only when you're ready to publish.
- Hacker News
- I’ve seen some terrible horrid nonsense from them and even the best compilers don’t use a third of the opcodes our modern CPUs boast of. Nobody understands the big compilers any more either, they’re all too huge. And soon AI will be “improving” hem too.
You want to see a beautiful compiler? Look at Plan 9’s compiler suite. A man could understand and even build on that.
by Glandalf - How does the resulting code compared to what a modern compiler gives me. I don't maintain compilers for a living, I maintain other code, which is ultimately longer and more complex than a C++ compiler. And so if my compiler, by becoming a little bit more complex, can make my resulting code a lot simpler because I don't have to do inline optimizations of various sorts, that makes my life much easier and is a good trade-off since there's a lot more programs in the world than there are compilers.by bluGill
- > even the best compilers don’t use a third of the opcodes our modern CPUs boast of
That’s not necessarily an indication of the weakness of compilers. It also could be an indication that hardware designers could leave out instructions.
X86, in particular, will have lots of them for backwards compatibility reasons (extreme example: the old 80-bit x87 FP stack)
There also are instructions that are expected to never get used by ‘normal’ compilers but cannot be removed because they only make sense in lower-level code such as those for switching between protection levels, implementing compare-and-swap, etc.
by Someone - > exceptions are slow
There are proposals to introduce better exceptions into C++. Like this: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p07....
But until it's not in the standard, people should use std::expceted instead.
by Panzerschrek - > Virtual vs static polymorphism
> std::visit over std::variant<A, B, C> is lowered to a switch over the active alternative.
> In this case, layout is probably doing more work than the dispatch mechanism itself.
Very likely because last time I checked visit lowers to a virtual call.
by mike_hock - Unremarked: debug build perf, perf-stability against minor edits, build-time bloat when heavily using std templates.by mwkaufma
- Trust the compiler - sure - but we can't change the whole program by using -ffast-math, unfortunately, so that particular one is out.by kzrdude
- I really dislike the complexity of modern C++ language specs, but does it obscure much detail about FP ops?
TL;DR:
A vast majority of the programmers I've worked with don't understand the nuances of FP in general, nor the various extents of IEEE-754 support in different programming languages.
So for important numerical programming, I think clarity regarding the FP operations being performed can be crucial. I'm just unclear if modern C++ is a significant factor for that.
by CoastalCoder - I like the Rust approach of adding operations like `algebraic_add` instead of supporting a compiler flag. This avoids undefined behaviour and keeps the complications from optimizations localized to code using these.
https://doc.rust-lang.org/std/primitive.f32.html#algebraic-o...
> Algebraic operators of the form a.algebraic_*(b) allow the compiler to optimize floating point operations using all the usual algebraic properties of real numbers – despite the fact that those properties do not hold on floating point numbers. This can give a great performance boost since it may unlock vectorization.
> The exact set of optimizations is unspecified but typically allows combining operations, rearranging series of operations based on mathematical properties, converting between division and reciprocal multiplication, and disregarding the sign of zero. This means that the results of elementary operations may have undefined precision, and “non-mathematical” values such as NaN, +/-Inf, or -0.0 may behave in unexpected ways, but these operations will never cause undefined behavior.
> Because of the unpredictable nature of compiler optimizations, the same inputs may produce different results even within a single program run. Unsafe code must not rely on any property of the return value for soundness. However, implementations will generally do their best to pick a reasonable tradeoff between performance and accuracy of the result.
by CodesInChaos - Don't trust your compiler. Your code is only fast if you're lucky.by chrka
- that's a nice example. on my M4, i measured 3.4s vs. 0.42s. honestly surprised there's ~10x improvement to be found.
as you've pointed out, you've literally micro-optimised this - isn't this what you'd expect? :)
by foxhill - I agree you can't trust your compiler, but you can control its behavior more reliably with __builtin_expect_with_probability
https://github.com/protocolbuffers/protobuf/commit/9f29f02a3...
by charleslmunger - Every time I see "use ranges and algorithms!" examples, I am baffled that apparently, I am supposed to find
more readable, concise, and easier on my eyes thaninline double algorithm_call(std::span<double const> xs) noexcept { return std::accumulate( xs.begin(), xs.end(), 0.0, [](double acc, double volts) { auto mv = calibrated_mv(volts); auto err = residual(mv); return weighted_square(err) + acc; }); }
Sure, there are some algorithms in <algorithms> that I'm rather not reimplement myself, but this one is not it.inline double raw_loop(std::span<double const> xs) noexcept { double sum = 0.0; for (double volts : xs) { auto mv = calibrated_mv(volts); auto err = residual(mv); sum += weighted_square(err); } return sum; }by Joker_vD - Great, now use some functions. From the library or your own, and see this complexity become manageable.
That's what abstraction is about.
by fooker - The first form is easier to send to 32 beefy cores or 1024 small CPUs or a Beowulf cluster or a GPU or people sitting in a room.by rzzzt
- You said "ranges and algorithms", but you didn't copy the third function which actually uses <range> library.
inline double ranges_pipeline(std::span<double const> xs) noexcept { auto costs = xs | std::views::transform(calibrated_mv) | std::views::transform(residual) | std::views::transform(weighted_square);
}return std::ranges::fold_left(costs, 0.0, std::plus<double>{});It's still a bit verbose, because C++ doesn't allow universal function call syntax. It will be even more concise in other languages like D.
by Erlangen
Related stories
How to sequence your own DNA at home
bradleywoolf.com · 370 points · 145 comments
You shouldn't trust Trusted Publishing
blog.yossarian.net · 26 points · 0 comments
Decoding the obfuscated bash script on a Uniqlo t-shirt
tris.sherliker.net · 1072 points · 181 comments
StreetComplete: Fixing OpenStreetMap, one tiny quest at a time
streetcomplete.app · 761 points · 182 comments
Every new car sold in the European Union must include a driver monitoring camera
allaboutcookies.org · 737 points · 969 comments
Chat Control 1.0 and 2.0 Explained
fightchatcontrol.eu · 645 points · 238 comments