6 ms·
Trust your compiler: Modern C++
- sylware 3mo ago[flagged]
- pjmlp 3mo agoQuite funny comment on the vibe coding age.
- sylware 3mo agoAnd yours is in no way related to mine...
- galangalalgol 3mo agoQuit poking at the openbsd maintainers. Jokes aside (I mean maybe they are one I don't know), it is at least a coherent opinion that inherently complex but critical software infrastructure would ideally be kept as simple and understandable as possible with all the correctness and verification apparatus staying out of the way so you can see what is there to be backdoored. I use rust primarily and like using it, but there are well over a hundred crates just in the front end, and llvm isn't simple. I do miss the days when I could know what each line did.
- sylware 3mo agoIf I am not too mistaken, microsoft rust syntax is close to c++ brain damage one, namely it is the issue more than any solution. Not to mention the cost of its runtime.
- benj111 3mo agoWhat has complex code got to do with it? Trusting trust was based on old C. You don't get much more minimal than that.
- never_inline 3mo agoDJ Bernstein seems to agree with you: https://blog.cr.yp.to/20240803-clang.html https://blog.cr.yp.to/20240803-clang.html
- sylware 3mo agoCommon sense agrees with DJ Bernstein.
- kzrdude 3mo agoTrust the compiler - sure - but we can't change the whole program by using -ffast-math, unfortunately, so that particular one is out.
- CoastalCoder 3mo agoI 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.
- CodesInChaos 3mo agoI 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-operators 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.
- kstrauser 3mo ago
- Glandalf 3mo agoI’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.
- bluGill 3mo agoHow 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.
- Someone 3mo ago> 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.
- mike_hock 3mo ago> 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.
- Panzerschrek 3mo ago> 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/p0709r0.pdf 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.
- Joker_vD 3mo agoEvery time I see "use ranges and algorithms!" examples, I am baffled that apparently, I am supposed to find 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.
- rzzzt 3mo agoThe 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.
- xyzzyz 3mo agoBoth of them have to be completely rewritten to make use of multiprocessing, so what exactly is the advantage?
- rzzzt 3mo agoThe first one too? Isn't that the map-reduce fork-join golden example of multiprocessing?
- cwzwarich 3mo ago`std::accumulate` is defined to have sequential semantics, so the analysis required to make it parallel is probably not that different than starting from the loop version. I guess you could have an alternate `accumulate_associative` that uses the same interface but assumes the reduction is associative and has unspecified evaluation order?
- mwkaufma 3mo agoUnremarked: debug build perf, perf-stability against minor edits, build-time bloat when heavily using std templates.
- chrka 3mo agoDon't trust your compiler. Your code is only fast if you're lucky. https://tiki.li/blog/lucky_code.html https://tiki.li/blog/lucky_code.html
- charleslmunger 3mo agoI 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/9f29f02a36ae0d2689ca7feebd2d07018436bcde https://github.com/protocolbuffers/protobuf/commit/9f29f02a3...
- foxhill 3mo agothat'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? :)
- ks6g10 3mo agoBut what is the resulting assembly? I would assume completely different!
- deterministic 3mo agoI trust the C++ committee to introduce new features in the most convoluted way possible, then spend the next 20 years trying to fix it, while adding even more syntax that makes my eyes hurt. Case in point: templates. They are essentially a pure functional programming language embedded inside C++, expressed in a verbose syntax that barely resembles the rest of the language, and somehow makes even Java look concise. It has been a slow-motion train wreck, with one questionable design decision after another. And a perfect example of why design by committee often leads to unnecessary complexity.