HN Theater @HNTheaterMonth

The best talks and videos of Hacker News.

Hacker News Comments on
CppCon 2019: Chandler Carruth “There Are No Zero-cost Abstractions”

CppCon · Youtube · 35 HN points · 16 HN comments
HN Theater has aggregated all Hacker News stories and comments that mention CppCon's video "CppCon 2019: Chandler Carruth “There Are No Zero-cost Abstractions”".
Youtube Summary
http://CppCon.org

Discussion & Comments: https://www.reddit.com/r/cpp/

Presentation Slides, PDFs, Source Code and other presenter materials are available at: https://github.com/CppCon/CppCon2019

C++ is often described as providing zero-cost abstractions. Libraries offer up facilities documented as such. And of course, users read all of these advertisements and believe that the abstractions they are using are truly zero-cost.

Sadly, there is no truth in advertising here, and there are no zero-cost abstractions.

This talk will dive into what we mean by "zero-cost abstractions", and explain why it is at best misleading and at worst completely wrong to describe libraries this way. It will show case studies of where this has led to significant problems in practice as libraries are designed or used in unscalable and unsustainable ways. Finally, it will suggest a different framing and approach for discussing abstraction costs in modern C++ software.

Chandler Carruth
Google

Videos Filmed & Edited by Bash Films: http://www.BashFilms.com

*-----*
Register Now For CppCon 2022: https://cppcon.org/registration/
*-----*
HN Theater Rankings

Hacker News Stories and Comments

All the comments and stories posted to Hacker News that reference this video.
Sep 26, 2022 · kccqzy on C++20, How Hard Could It Be
> std::unique_ptr<Foo> 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

ncmncm
Not breaking binary compatibility is a pure good.

The standard could pull unique_ptr in as a built-in feature with a different name, and retain backward compatibility at cost of an unfortunate redundancy. Code using unique_ptr would gradually transition to the new thing.

Of course each would have a move constructor from the other, to ease the transition.

Yes, tech talks don't need to be boring and I agree on many points on the post. But also not just 12 slides, more like hundreds of slides.

One example I have is Chandler Carruths talk in CppCon 2019 "There Are No Zero-cost Abstrations" https://youtu.be/rHIkrotSwcc Chandler presents his point clearly and goes deep enough into the subject.

Jan 01, 2022 · cjfd on Writing New System Software
This appears to be incorrect. I have seen a cppcon talk about that. I think it may have been this one: https://www.youtube.com/watch?v=rHIkrotSwcc . On the other hand, it is a very, very tiny minority of programmers who have to care about an overhead as low as this. Most of us should not waste one microsecond of our thinking time on this.
Oct 01, 2021 · 1 points, 0 comments · submitted by michaelsbradley
I wonder if it's time to move past that original meaning, or perhaps invent a new catchy term to describe the problem. I'm reminded of this CppCon talk

https://youtu.be/rHIkrotSwcc?t=888

There are no zero cost abstractions. (According to Chandler Carruth, and he makes a convincing point). https://www.youtube.com/watch?v=rHIkrotSwcc
PragmaticPulp
That’s a disingenuous misinterpretation of what “zero-cost” means. Zero-cost refers to runtime performance of compiled code.

It’s also disingenuous to pretend there isn’t some downside to not using the abstraction. Obviously you need to evaluate the tradeoffs.

High level code itself is a sort of abstraction. We could write raw assembly all the time to remove that abstraction and associated costs, but clearly that’s not very productive.

quotemstr
Chandler is simply wrong here. Every single line of code has a cost. There's nevertheless massive benefit to that cost not occuring on millions of end user machines and instead only once in the mind of the developer.
HNisBaizuo
Even if an abstraction appears free, you pay opportunity cost by forming a reliance upon it.
josephcsible
Most people consider "cost" in "zero-cost abstraction" to refer to runtime cost. That presentation is saying that if you let it refer to other kinds of cost too, then nothing is zero-cost, but to me that just means he picked a bad definition.
dkersten
And even then, I've often heard it stated as "zero cost if you don't use it", eg, exceptions have zero runtime cost unless an exception is actually thrown, so, given that they should only be thrown in exceptional cases, that really shouldn't impact your program. (Whether or not its truly zero cost depends, I guess, eg does memory count as cost? What if it makes something not fit in cache?)
bigcheesegs
I love that exceptions are the classic case for this, because it's not even true that "zero-cost exceptions" are zero _runtime_ cost on the non exceptional path. The most trivial example is they block vectorization.
TeMPOraL
Well, exceptions shouldn't be the classic case here, because - as you say - they're typically not zero cost.

Zero-cost abstraction is what you get when your abstraction gets compiled away to nothing. Like properly written value wrappers in C++, or (presumably) newtype in Rust. These things disappear from final assembly.

jcelerier
The definition of zero-cost abstractions, as introduced in C++, was that "it can't be slower that code than the equivalent hand-written code" (e.g. code not using the abstraction).

In that regard, exceptions are interesting as if you're on the happy path (which should be 99.999% of the time - a normal program that uses exceptions as error handling method should not encounter any exception if you do a `catch throw` on an average run of the software), they can cost less than return-value-based error handling (https://nibblestew.blogspot.com/2017/01/measuring-execution-...). If you're on a "sad path", though, they will cost more.

What is pretty sure is that since compilers learned to put the "sad" path in .cold section, the code size issue has become a 100% non-issue, the "sad" path won't bloat the hot, exception-less path ; in my experience, exceptions are in cases that matter a negative-cost abstraction.

anon1608
As always with microbenchmarks...

  struct Error* create_error(const char *msg) {
    struct Error *e = malloc(sizeof(struct Error));
    e->msg = strdup(msg);
    return e;
  }

Is it measuring exceptions vs return codes, or creation cost of std::runtime_error(const char *) (small string opt?) vs malloc() + strdup?
The other time that you saw it was also probably me. It's from this talk, which is about how a large amount of generated protocol buffer code at Google led to a quadratic increase in compile times: https://youtu.be/rHIkrotSwcc?t=720.

TL;DW: The reasoning is because if you use a distributed build system, then your compile time is gated by the file with the longest compile time (tail latency). The more files you have, the greater chance that one of them takes a long time. When you generate source files, you tend to produce more files than if you didn't.

Most users don't use a distributed build system to compile the kernel, so on further thought, in that case compile times probably scale closer to linear with the number of translation units. But wasted cycles are still wasted cycles, and regardless of how exactly compile times scale, you should still consider the cost of longer compile times when you duplicate code.

With regards to link time optimization: sophisticated analyses take superlinear complexity: https://cs.stackexchange.com/questions/22435/time-complexity....

Disclaimer: I work at Google.

r-r-r
Just a couple of weeks ago, I had to use some auto generated serialization of some API to reproduce an issue. This was well over 100k lines, mostly in a single function with a lot of long lived variables. It took gcc over 2 hours to compile this. I was already thinking how to attempt to improve it by scoping some variables, creating tables of variable values, to reduce symbols and overall splitting it into several functions and compilation units. But before doing any work on it, I wanted to see that I'm not missing anything and ran with clangs relatively new -ftime-trace (Which really helped me several months ago on a different project, finding 30% of the time spend on a common template all over the place) but "unfortunately" it compiled too quickly with clang (Just a few minutes), and even faster with the traces disabled, so I couldn't justify looking at it any long for something I'd use super rarely... :)

But this is just luck. In a different project, we had to load quite a bit of data into c++ code and we chose to go with a sep. format and just load it from the c++ code. There was no point in paying for something that happens so often in compilation time. It had two parts essentially and one of them describing some api calls ended up as json for easy editing while another made sense as a separately linked in object.

As a side note, some parts of compilers I've worked on were definitely at least n^2, you just have to be careful to bail out when you have a decent enough approximation or you've spend too much time on specific task. A lot of this comes from only optimizing what you have to (A certain n^3 algorithm held really well for some stuff, until we hit an edge case where it didn't :) but also from the difficulty of the domain.

Not sure if this is sarcastic, but...

1) In dynamic languages, it's not possible to detect whether a function is used or not in the general case. For example, consider string accesses on objects. If the compiler is not sophisticated enough to resolve the set of possible strings at compile time (or such analysis would unacceptably increase compile times), then you can't shake out unused methods on that object. [1]

2) For languages like C and C++, the compiler cannot tree shake because it only knows about a single file at a time (translation unit, to be precise). You would have to rely on link-time optimizations to effectively tree shake, but LTO is not well-supported by all toolchains.

Tree shaking also has a cost that I mentioned earlier -- it increases compile times. Both LTO and tree shaking in dynamic languages increase compile times superlinearly [citation needed] wrt. the size of your application. As other commenters have mentioned, it's better to avoid including unnecessary libraries in the first place.

There are no cost free abstractions, and that applies to tree shaking as well. https://www.youtube.com/watch?v=rHIkrotSwcc

[1] For the pedants: yes, I know resolving the possible set of values (stricter than "all possible values for that type") for a variable is undecidable in the general case.

plasticchris
So we do this all the time to meet code size constraints in embedded sw, just put all functions in their own sections and tell the linker to discard unused sections. It's been supported for years by gcc.
amelius
You could perhaps better use a lazy-loading strategy instead of a static analysis. (But this would change the semantics in case of an existing language that allows side-effects while loading modules, unless you have a lazy strategy for them too ...; and then there are the errors you'd have to deal with)
tylerhou
To truly achieve the same thing as "tree shaking," the function call overhead would be abysmal. You'd have to check whether the module was loaded already (with synchronization if your program is multithreaded). For single threaded programs, you could avoid this by hot-patching your machine code, but there's no way around some synchronization barrier in multithreaded programs [1]. In JavaScript (or any language where you want to avoid sending a large bundle over the network), you'd incur the latency of a network request for the first call to any function.

You're right that people are already splitting apps into bundles, but that is usually done at the page level.

[1] You could probably avoid having to take a mutex after the first call to the function with self patching code, but that sounds incredibly ugly and could have other implications (self-modifying code could be detected as a virus; could be used as a gadget to exploit some other vulnerability).

amelius
Isn't self-modifying code already the norm, with JIT compilers?
mcguire
... which is why JIT compiled code is slower in some circumstances.
tylerhou
I am under the impression that JIT compilers do not modify the compiler’s own bytecode. They write generated code into a separate data region and mark that region as executable. If the code needs to modified, then control transfers back to the compiler which can mark the region as writable again.

The same can be done for a binary and it’s own code, but I wonder if it’s used as a signal in antivirus protections if done too often.

https://en.m.wikipedia.org/wiki/W%5EX

Sep 06, 2020 · Rusky on C++20 has been approved
This is probably not what was meant, but interestingly unique_ptr actually isn't zero-cost on some ABIs: https://www.youtube.com/watch?v=rHIkrotSwcc
Close but not exactly https://youtu.be/rHIkrotSwcc
Sep 03, 2020 · mehrdadn on The problem with C
With the caveat that I am NOT suggesting this justifies choosing C over C++, I just wanted to mention this talk about how "zero-cost abstraction" is an idealism, not necessarily a reality: https://www.youtube.com/watch?v=rHIkrotSwcc&t=17m30s

That said, I tried to reproduce something similar, and it seems the issue only occurs in my example due to external linkage (adding 'static' fixes it)... but I can't claim this will always resolve the issue: https://gcc.godbolt.org/z/1vbqo3

virgilp
I watched only a tiny part of that talk but the speaker claims "there are no zero-cost abstractions" and justifies it with.... unique-ptr??? I mean, who doesn't know that smart pointers can be slower than raw pointers? They're not there because they're "zero-cost", but because the improvement in reliability & readability more than makes up for the runtime cost in the vast majority of situations.
mehrdadn
He's comparing unique_ptr to just plain old-fashioned new and delete. Is it obvious to you what the actual cost is in moving between these? It's not really obvious and probably not what you think. I would listen to the full section of the talk on unique_ptr.
virgilp
I developed an optimizing C++ compiler in my youth (one that was actually used in production, not just a pet project). I probably know the actual cost, and also know it may depend on compiler.

But that's not the point - the point is that smart pointers never claimed to be zero-cost abstractions, AFAIK.

mehrdadn
> But that's not the point - the point is that smart pointers never claimed to be zero-cost abstractions, AFAIK.

> I mean, who doesn't know that smart pointers can be slower than raw pointers?

Nobody... on what basis? If you're not in that set of people, that doesn't imply the set is small or empty. Googling suggests lots of people have given such advice, and, may I suggest, it's not because they were stupid.

virgilp
> Nobody... on what basis?

I don't understand the question (I didn't use the word "nobody"). I stand by the first claim (library writers & ISO C++ commitee never claimed that smart pointers are zero-cost abstractions; so why would people believe that? How did that claim start?).

The second (rhetoric) question... well, of course people would occasionally believe random stuff, for God's sake, we have (non-stupid!) people who believe COVID is a conspiracy. That said - there's no basis in the belief that smart pointers are zero-cost, so it's funny to make a talk "debunking" it - to me it looks similar to a talk that "COVID is real, guys!". I was genuinely surprised this talk is needed. I think if this is really a wide-spread belief, a more insightful talk would be about how it got to be a wide-spread belief :)

UncleMeat
Chandler is on the cpp committee and personally said he was surprised by this cost when passing unique ptr as a parameter, as were many on the committee.

So the evidence against your claim is literally within the talk.

virgilp
Ok, I guess I'll look at the talk/ didn't know the guy was in the C++ committee. Still, I personally had to fight the other way around - to convince people that "no, smart pointers don't have significant overhead over raw pointers; and the optimizations/ custom smart pointers that you're doing now, to the extent that they have any effect at all, will likely be rendered obsolete by future compiler versions & libraries".

I could understand a CPP committee member expecting a certain compiler to optimize a certain situation; however, the blanket statement that "smart pointer X is zero-cost" is more than strange, given the fact that it's always bound to be implementation-dependent, and there isn't one single C++ compiler (or even a "canonical" one) so that you could make that claim, at all. I find it really suspicious.

[edit] I watched the part of the talk - I think he was a bit surprised that has favorite compiler didn't perform that optimization, and it took him some investigation to see why. I don't think he truly expects smart pointers to be "zero-cost abstractions", I think he was just surprised that his compiler didn't optimize better that particular situation and had to dig in to find why. I still find the whole presentation a bit of a misleading stunt - there _are_ zero-cost abstractions (for some definitions of "cost" of course; and/or in some situations). E.g. in rust [1]. And even in C++ - a local unique_ptr is _probably_ a zero-cost abstraction!

BTW.. in his example - just use move semantics, put the pointer as the 5th argument, and you'll probably have the same runtime cost (ie both the raw pointer and smart pointer methods will compile to same code; since the ABI no longer allows you to pass the raw pointer via registers, it goes to the stack, so you have the additional load that bothered him in the raw pointer case, too).

[1] https://medium.com/ingeniouslysimple/rust-zero-cost-abstract...

UncleMeat
> BTW.. in his example - just use move semantics, put the pointer as the 5th argument, and you'll probably have the same runtime cost

He talks about this. C++ doesn't have destructive moves like rust does, which is the root cause of why you cannot make the unique_ptr cost zero. It'd take an ABI change to fix this. This is precisely why the scenario is interesting. A lot of people (including you) thought that "just use move semantics" would solve it.

mehrdadn
I've been wondering why they don't permit destructor elision after a move into a newly constructed parameter. I have a hard time seeing how this would break reasonable code, and even then, we can probably find a backward compatible workaround (or rely on a compiler flag). Any idea?
UncleMeat
ABI. C++ defines rigorous calling conventions that don't permit this. There are some on the committee who want to permit ABI breaks to enable these sorts of optimizations (in this case, destructive-move) but the committee in general has been super hesitant to permit ABI breaks.

It won't break reasonable code. The problem is it breaks interop with compiled binaries.

mehrdadn
I'm confused, this seems unrelated to ABI. It's entirely up to the caller whether to subsequently destroy the moved-from object or to skip doing so. Whether or not the caller does so is irrelevant to the call or how the parameter gets passed, and it wouldn't affect the moved-to object (whose destructor always runs). The destruction of the moved-from object (if not elided) happens long after the call has returned - at the end of the object's scope.

I think you're confusing my proposal with the pass-in-register proposal? Or maybe I'm missing something.

virgilp
Thing is... talks like these give a wide body of application developers ammunition to say "smart pointers are slow, we need to use raw pointers!". Which is incredibly damaging. In many real-life situations, the difference literally doesn't matter (one more memory move? pfft... I'd take the whole complicated, exception-safe function body over knowing that in case of an exception I won't have a memory leak)

There are indeed cases where this sort of difference in performance matters; But those are a vanishingly small group of people; and as far as those people are concerned - they typically look at assembly anyway.

UncleMeat
> smart pointers are slow, we need to use raw pointers!

He addresses this in the QA at the end. Chandler explicitly says "I still believe you should use unique_ptr and pass it by value". If somebody watches this video and takes away "don't use smart pointers" then they weren't paying attention.

bregma
The "zero-cost abstraction" concept is that you don't pay for something you don't use. If you use a smart pointer, you pay for it. If you don't use a smart pointer, it's zero cost.

Any argument that smart pointers are not zero cost to the language because they have a cost when you use them is a classic straw man argument.

mehrdadn
I think there might be a terminology mixup here but he probably meant zero-overhead abstraction. And in any case, the point he's making is not a strawman argument.
bregma
OK, let's substitute "overhead" for "cost in the argument.

Premise one: C++ has zero overhead for most of its features, like smarts pointers: if you don't use them, they cost nothing.

Premise two: using features like smart pointers adds a cost to your C++ program.

Conclusion (due to the strawman logical fallacy): C++ is not zero overhead because there exists a feature that has a cost if you use it.

I'd like to see the reasoning that leads to the same conclusion without resorting to the strawman logical fallacy.

mehrdadn
Did you watch the video and notice he didn't just talk about smart pointers?
mehrdadn
He talked about more than just unique_ptr if you actually watch the video, but I don't even get why you're dying to have such a pointless argument here. When you see free lunches offered somewhere, do you start arguing with people how everyone is wrong and there is such a thing as a free lunch too? Is it so hard to actually take the point and just move on instead of dissecting it like a mathematical theorem?
Re: metamap being zero cost

I thought there were no zero cost abstractions in C++?

https://youtu.be/rHIkrotSwcc

Still, it isn’t RoR or anything JS so it has that going for it.

matt42
Yes zero cost is subjective. It has a cost at least at compile time. But you can see metamap is a like loop unrolling for objects (if you think loop unrolling is zero cost then metamap too).
It's not essentially free

https://m.youtube.com/watch?v=rHIkrotSwcc

17:40

quietbritishjim
I'd rather you'd just stated your objection instead of making me watch a bit of video. Thanks for at least including a time stamp. For the benefit of others: Using a std::unique_ptr adds exception handling code to the functions that use it to guarantee cleanup when exceptions are thrown. Of course, that extra code is not actually executed if the exception is not thrown.

I was responding to a comment that claimed that std::unique_ptr used locks to transfer ownership, and my objection to that is not disputed by what your saying.

You're making the point that exception-safe code is less efficient than exception-unsafe code. I do not know enough about that topic to comment on that matter, I was never trying to say anything about it in my previous comment. But I would like to point out that Rust has an analogue of exceptions: panics (although you can change their behaviour to abort() instead of unwind). So it must have something similar to unique_ptr's unwind handler anyway.

eska
Nah, you missed the main point, it doesn't deal with exceptions. But since you misuse the downvote feature after I looked for the video and timestamp for you, I guess you don't want to be proven wrong anyway. Have a nice day.
quietbritishjim
You can't downvote direct replies to your comments, you'll notice the downvote button is missing for you on this comment of mine. That must be someone(s) else.

What do you mean by "it" in "it doesn't deal with exceptions"? Rust? Or the C++ snippet on the left hand side in the presentation with the shorter assembly output?

Edit: Since I'm still apparently not clear on your main point, this might be irrelevant, but here [1] is a Godbolt link showing that Rust functions have similar panic-safe unwind code to the C++ exception unwind code shown in the presentation. Admittedly the Rust version does seem a bit shorter (comparing just the actual function, at the bottom of the Godbolt output) and I'm afraid I don't know enough assembly to be able to analyse the difference. But I think the principle is similar.

[1] https://godbolt.org/z/K1xPr3

> However, do you have any bench / source for std::unique_ptr ? You are the first one I hear giving critics on it.

Chandler Carruth talks about this at CppCon 2019 [0]. From a quick review he says part of the reason std::unique_ptr isn't zero-cost right now is:

- Objects passed by value (like std::unique_ptr) are passed on the stack instead of in registers, and changing that will be an ABI break

- No destructive move means extra temporaries will be created/retained

[0]: https://youtu.be/rHIkrotSwcc?t=1046

Feb 17, 2020 · blux on Move, simply
The non-destructiveness of move semantics in C++ is very annoying. It causes for example `std::unique_ptr` to not be a zero-cost abstraction in all cases. See https://www.youtube.com/watch?v=rHIkrotSwcc for a nice expose on this.
Feb 03, 2020 · leni536 on ABI – Now or Never [pdf]
The video: https://www.youtube.com/watch?v=rHIkrotSwcc

I wrote up a reddit post for a possible workaround for removing the overhead. It's standard C++, no ABI break is required. It's not without caveats though: https://www.reddit.com/r/cpp/comments/do8l2p/working_around_...

Oct 19, 2019 · 25 points, 1 comments · submitted by skohan
paulddraper
Does the "point a pointer" problem get easier in Rust?

(Not that Rust doesn't have its own non-zero-cost abstractions, but it seems like this is zero-cost for Rust.)

Oct 14, 2019 · 1 points, 0 comments · submitted by mpweiher
Oct 09, 2019 · 1 points, 0 comments · submitted by dgellow
Oct 08, 2019 · 1 points, 0 comments · submitted by tpush
Oct 07, 2019 · 6 points, 0 comments · submitted by pjmlp
HN Theater is an independent project and is not operated by Y Combinator or any of the video hosting platforms linked to on this site.
~ yaj@
;laksdfhjdhksalkfj more things
yahnd.com ~ Privacy Policy ~
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.