Live data from Hacker News

C++20, How Hard Could It Be

docs.google.com

391–400 of 444 posts

Re: C++20, How Hard Could It Be

#391

Earlier quoted context omitted.

There's a lot there. Let's get the technical part done first. Historically after you create object files the linker doesn't care what c++ standard the source was. So you could carefully combine different standards. I guess I have to establish I'm talking about the GNU toolchain here and that it's been a few years since I've done this. I'll try it again when I get home, maybe that all blows up now. Now about the other…

> Historically after you create object files the linker doesn't care what c++ standard the source was. Mechanically this is true, but just because we can link object files together doesn't mean the resulting program makes sense. Suppose I have an object file I made with GCC's copy-on-write C++ 98 strings and then I linked that to an object file I made with GCC's modern C++ 11 short string optimised strings. If these…

Maybe you have a deeper understanding of the GNU GCC toolchain than I do but I'm able to link together languages with far more dramatic divergences than that.

For instance, Go and C : https://go.dev/doc/install/gccgo (it's pretty far down, let me quote: "The name of Go functions accessed from C is subject to change. At present the name of a Go function that does not have a receiver is prefix.package.Functionname. The prefix is set by the -fgo-prefix option used when the package is compiled; if the option is not used, the default is go. To call the function from C you must set the name using a GCC extension.")

I just had a small C program call a fmt.Println from a go library at my console to confirm. extern the declaration in C to match the calling convention of go, compile them to objects, then ld with the appropriate libs.

Of course you can break the friendship they can have, this is programming, that's easy to do.

This can be demonstrated with different C++ versions as well. The C/Go example was meant to show how extremely different languages can interact when you're careful enough in your build process.

Re: C++20, How Hard Could It Be

#392

Earlier quoted context omitted.

Are all of C++20’s volatile deprecations un-deprecated in C++23? This doc from 2020 lists all: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p21... But this proposal from 2021 suggests only undeprecating bitwise compound operations: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p23...

Sorry, you're right, my bad. People are actually writing compound arithmetic assignment ops on volatiles, huh.

You got my hopes up about un-deprecation :) but I was glad to at least learn about the proposals

Re: C++20, How Hard Could It Be

#393
post #370

Earlier quoted context omitted.

The negative performance impact of GC in performance-engineered code is neither small nor controversial, it is mechanical consequence of the architecture choices available. Explicit locality and schedule control makes a big difference on modern silicon. Especially for software that is expressly engineered for maximum performance, the GC equivalent won't be particularly close to a non-GC implementation. Some important…

When people complain about "negative performance impact of GC", often they're actually bothered by badly designed languages like Java that force heap-allocation of almost everything. I think this might have been fixed in latest versions of Java, though, not sure if value types are already in the language or just coming soon. Aside from that, it's my understanding that GC can be both a blessing and a curse for perform…

In theory, a GC should never be faster than manual memory management. Anything a GC can do can be done manually, but manual management has much more context about appropriate timing, locality, and resource utilization that a GC can never have. A large aspect of performance in modern systems is how effectively you can pipeline and schedule events through the CPU cache hierarchy.

There are a few different ways a GC impacts code performance. First, even low-latency GCs have a latency similar to a blocking disk op or worse on modern hardware. In high-performance systems we avoid blocking disk ops entirely specifically because it causes a significant loss in throughput, instead using io_submit/io_uring. Worse, we have limited control over when a GC occurs; at least with blocking disk ops we can often defer them until a convenient time. To fit within these processing models, worst case GC latency would need to be much closer to microseconds.

Second, a GC operation tends to thrash the CPU cache, the contents of which were carefully orchestrated by the process to maximize throughput before being interrupted. This is part of the reason high-performance software avoids context-switching at all costs (see also: thread-per-core software architecture). It is also an important and under-appreciated aspect of disk cache replacement algorithms, for example; an algorithm that avoids thrashing the CPU cache can have a higher overall performance than an algorithm that has a higher cache hit rate.

Lastly, when there is a large stall (e.g. a millisecond) in the processing pipeline outside the control of the process, the effects of that propagate through the rest of the system. It become very difficult to guarantee robust behaviors, safety, or resource bounds when code can stop running at arbitrary points in time. While the GC is happening, finite queues are filling up. Protecting against this requires conservative architectures that leave a lot of performance on the table. If all non-deterministic behavior is asynchronous, we can optimize away many things that can never happen.

A lot of modern performance comes down to exquisite orchestration, scheduling, and timing in complex processes. A GC is like a giant, slow chaos monkey that randomly destroys the choreography that was so carefully created to produce that high-performance.

Re: C++20, How Hard Could It Be

#394

Earlier quoted context omitted.

The way I look at it is we're in a transitional period. A language like Go or Rust can replace some of the C++ lift, but we're not sure because they're not a large body of experience with those languages. I suspect, but can't say with any certainty, that we'll wind up in a world where the use case for C++ shrinks significantly. Rust and Go will eat into the share of new Greenfield systems that would have normally gon…

Rust can't easily bind to many modern, high performance C++ libraries because it lacks a fair amount of the semantics of C++.

I don't think this is a material limitation. Anecdotally, we have few issues with sensibly blending C++20 and Rust. Any exported APIs will need to be circumscribed anyway for integrating with other languages.

Re: C++20, How Hard Could It Be

#395

Earlier quoted context omitted.

In a language like C++ returning status codes means that callers can and will ignore it, even when they shouldn't.

Since C++17, using [[nodiscard]] can help with that.

Indeed, Google's implementation of Status/StatusOr requires explicit handling of those objects, they cannot be discarded automatically.

Re: C++20, How Hard Could It Be

#396
post #135

Earlier quoted context omitted.

You could focus on those 460 pages but I'll raise 2 points: 1. There's still a lot of complexity and ambiguity you can fit in 460 pages. This presentation notes one example of decrement operators on volatile variables being deprecated because the behaviour was undefined; and 2. Can you really separate the standard library from the language at this point? Things like move semantics depend on std. Does anyone actually…

1. The problem you've highlighted (pre/post increment of volatiles) is present in C, and hasn't been addressed there. This is a problem in c2x on compiler explorer for example. So i'd probably say laying this at the feet of the overly large C++ language spec isn't fair. I'm going to probably say it's been there since K&R C days (I think volatile was supported even that far back, but my memory is a bit hazy about such…

Both the qualifiers const and volatile were new in ANSI C (which became ISO C89). In fact I believe the idea of type qualifiers in C is imported from (pre-standard) C++, thus it's Bjarne's fault.

The general idea (if we force the optimiser to emit the memory access then we can abuse that to do MMIO) is older than ANSI C and as I understand it begins when peephole optimisers begin to first make the "obvious" trick not work in C compilers, but I don't know when it became the volatile qualifier.

As in C++ the correct fix is to use dedicated intrinsics. JF Bastien wrote up C++ intrinsics for this as a template, obviously the C intrinsics would not be a template, but the general idea applies. In reality your hardware does not implement crazy nonsense like a 196-bit unaligned non-tearing memory fetch, so you don't need customisable intrinsics.

e.g. maybe C gets __volatile_load_64(ptr) and that's 64-bit aligned fetch from the address in ptr, there would be a handful you actually need for 8-bit, 16-bit, 32-bit, 64-bit, maybe 128-bit, loads plus stores and perhaps implementations are asked to offer any special cases for their platform, I can imagine unaligned 32-bit is plausible on x86 for example, maybe some DSP has 24-bit, that sort of thing.

The idea is the intrinsic emits the same CPU instructions which are what happens for volatile access today, but as intrinsics they don't give the false impression you can do other stuff, this isn't really memory even though the CPU instructions are memory access instructions.

Re: C++20, How Hard Could It Be

#397

Earlier quoted context omitted.

The "idiomatic" version is slowly getting slower because the idioms are getting higher-level and more expressive. If you don't use the idioms, you can have the same speed. Smart pointers (including unique_ptr ) have non-zero overhead compared to Foo*. The object oriented parts can introduce significant slowdowns. Template code can have huge code footprints if you are not careful, which slows things down. If you are l…

The idiomatic version has gotten faster since C++03 overall thanks to move semantics. std::unique_ptr doesn't have any overhead over Foo* in code where semantics is the same (i.e. you want to delete when going out of scope) on any sensible ABI - it has the same storage size, and all operations are trivially inlineable to the same exact thing you'd do with a raw pointer. About the only time I can think of where it can…

> std::unique_ptr doesn't have any overhead over Foo* in code where semantics is the same

Not true. And you just refuted yourself by being up the ABI question. You should watch Titus Winters' presentation on this topic where he compiles code using unique_ptr and raw pointers, compares the assembler produced, and explains why unique_ptr has non-zero overhead. The overhead is indeed coming from the ABI. Google has been wanting the next C++ to break ABI but they didn't get enough votes in the standard committee.

https://youtu.be/rHIkrotSwcc

Re: C++20, How Hard Could It Be

#398

Earlier quoted context omitted.

> Don't pass smart pointer arguments around on hot paths. oh this so much.. I remember optimizing a particle system which would copy a shared_ptr for each particle on every update operation... IIRC just switching to references ended up being 10+ times faster

If you can design your system so you rarely if ever use pointers, that would be the single best thing you can do for your C++ codebase I like to enlighten my interns every summer about the reality of pointers, and how much of a noob trap they are, and that yes, their professor lied to them in some ways

No post body was provided.

Re: C++20, How Hard Could It Be

#399
post #319

Earlier quoted context omitted.

This sounds nice but is not true. The bigger the system is, the more value you get from exceptions. The cost is that cleanup operations have to be in destructors. Do that, and exceptions demand almost no attention. Problems show up only when some prima donna declares throwing exceptions from what they call isn't allowed.

You literally provide no evidence of your view, just assertions. Not even reasoned argument or examples. Just assertions. Assertions that lots of people who are serious experts in c++, library design, etc, are prima donnas. You see how that isn't particularly helpful or constructive to discussion, right? Maybe you'd like to at least point out the very large scale systems that are using and getting benefits from excep…

I hope we can all agree that we want the overwhelming majority of the lines of code we write, and of the cycles our programs execute, to be about solving an actual problem representing the purpose the program was written. Lines and cycles fooling with internal junk are overhead, waste.

And, we want our functions to be small, do one thing, and be easily composed.

The problem with Result and their ilk is that they pile on overhead at function-call boundaries, exactly the place where you need design to be fluid.

The great insight behind C++ was that cleanup, packaged and applied automatically, eliminates opportunities for mistakes, freeing up our attention. Exceptions apply that to error handling by running the same, well tested code when errors happen, and gathering error events to a place where we are equipped to do something useful about them.

Now that we often don't even need to code destructors anymore -- the compiler does it better -- our attention is freed for real-world problems. And, not needing to worry about propagating errors up the stack, we can focus on arguments and control moving down and results up.

Attention is easiest to manage in a small program. There just isn't as much to think about. In a big program, with many people involved, it costs a lot more to deal with extra junk, choices about how to bubble errors out. Structuring error handling into destructors and exceptions provides a common framework that burns no discussion time. Everybody gets to concentrate on solving the actual problem, without distraction by incidentals.

We would not be having this discussion if Google had not imposed its rule banning exceptions, which we know was imposed just to accommodate legacy code that did not have destructors. Now they have a thousand times as much code, still without good destructors. Wouldn't it have been less work to fix that code, then, than to impose its costs on everyone today?

Re: C++20, How Hard Could It Be

#400
post #366
post #332

Earlier quoted context omitted.

Then you are writing bad C++ code. The new features are added specifically because they enable writing better programs. Avoiding them means you are choosing not to write better programs. It is allowed, but not a thing to brag about. That does not mean every program has to use every feature. But when there is a choice between the new way and the old way to do something, the new way is very probably better. Passing a r…

Absolutely, but none of those are really that new. I guess I'm talking more about boost libraries and that stuff. I just can't be bothered and it doesn't matter for anything I need to write.

They are not especially new, but they are not in C.

C is the language that invites in mistakes. The less C we code, the fewer mistakes we ship.

Post reply on HN