Live data from Hacker News

The Performance Impact of C++'s `final` Keyword

16bpp.net

341–350 of 385 posts

Re: The Performance Impact of C++'s `final` Keyword

#341

The only thing worse than no benchmark is a bad benchmark. I don't think this really shows what `final` does, not to code generation, not to performance, not to the actual semantics of the program. There is no magic bullet - if putting `final` on every single class would always make it faster, it wouldn't be a keyword, it'd be a compiler optimization. `final` does one specific thing: It tells a compiler that it can b…

'Final' cannot be assumed without complete knowledge of all final linking cases, and knowledge that this will not change in the future. The latter can never be assumed by a compiler without indication.

"In theory" adding 'final' only gives a compiler more information, so should only result in same or faster code.

In practice, some optimizations improve performance for more expected or important cases (in the compiler writer's estimation), with worse outcomes in other less expected, less important cases. Without a clear understanding the when and how of these 'final' optimizations, it isn't clear without benchmarking after the fact, when to use it, or not.

That makes any given test much less helpful. Since all we know is 'final' was not helpful in this case. We have no basis to know how general these results are.

But it would be deeply strange if 'final' was generally unhelpful. Informationally it does only one purely helpful thing: reduce the number of linking/runtime contexts the compiler needs to worry about.

Re: The Performance Impact of C++'s `final` Keyword

#342
post #287
post #35

Earlier quoted context omitted.

C++ doesn't have the fragile base problem, as members aren't virtual my default. The only concern with unintended inheritance is with polymorhpic deletion. "final" on class definition disables some tricks thag you can do with private inheritance. Having said that "final" on member functions is great, and I like to see that instead of "override".

All OOP languages have it, the issue is related to changing the behaviour of the base class, and the change introducing unforceen consequences on the inheritance tree. Changing an existing method way of calling (regular, virtual, static), changing visibility, overloading, introducing a name that clashes downstream, introducing a virtual destructor, making a data member non-copyable,...

> All OOP languages have it, the issue is related to changing the behaviour of the base class, and the change introducing unforceen consequences on the inheritance tree.

C++ largely solves it by having tight encapsulation. As long as you don't change anything that breaks your existing interface, you should be good. And your interface is opt-in, including public members and virtual functions.

Re: The Performance Impact of C++'s `final` Keyword

#343

The only thing worse than no benchmark is a bad benchmark. I don't think this really shows what `final` does, not to code generation, not to performance, not to the actual semantics of the program. There is no magic bullet - if putting `final` on every single class would always make it faster, it wouldn't be a keyword, it'd be a compiler optimization. `final` does one specific thing: It tells a compiler that it can b…

> `final` does one specific thing: It tells a compiler that it can be sure that the given object is not going to have anything derive from it. ...and the compiler can optimize using that information. (It could also do the same without the keyword, with LTO.)

LTO can only apply in specific situations though, if there is any possibility that a plugin derived from the class LTO can do nothing.

Re: The Performance Impact of C++'s `final` Keyword

#344

Earlier quoted context omitted.

In general purpose scenarios, particularly in codebases which have high amount of abstractions, use ASP.NET Core and EF Core, parse and de/serialize text with the use of JSON, Regex and other options, have network and file IO, and are deployed on many-core hosts/container images. There are a few articles on msft devblogs that cover from-netframework migration to older versions (Core 3.1, 5/6/7): - https://devblogs.mi…

Cheating. All of the 6x performance improvement cases seem to be related to using the .net based Kestrel web server instead of IIS web server, which requires marshalling and interprocess communication. Several of the 2x gains appear to be related to using a different database backend. Claims that regex performance has improved a thousand-fold.... seem more troubling than cause for celebration. Were you not precompili…

Something closer to a "pure codegen/runtime" example perhaps: I have data showing Roslyn (the C# compiler, itself written in C#) speeds up between ~2x and ~3x running on .NET 8 vs .NET 4.7.1. Roslyn is built so that it can run either against full framework or core, so it's largely the same application IL.

Re: The Performance Impact of C++'s `final` Keyword

#345
post #56

Earlier quoted context omitted.

In my opinion, the only things that really matter are algorithmic complexity and readability. And even algorithmic complexity is usually only an issue a certain scales. Whether or not an 'if' is faster than a 'switch' is the micro of micro optimizations -- you better have a good reason to care. The question I would have for you is was your bunch of ifs more readable than a switch would be.

Yeah, and it's not like I didn't know how to do the stuff I was doing with a switch, I just don't like switches because I've forgotten to add break statements and had code that appeared correct but actually a month down the line. I've also seen other people make the same mistakes. ifs, in my opinion at least, are a bit harder to screw up, so I will always prefer them. But I agree, algorithmic complexity is generally…

> in my opinion at least, are a bit harder to screw up, so I will always prefer them

My experience is the opposite - a sizeable chain of ifs has more that can go wrong precisely because it is more flexible. If I'm looking at a switch, I immediately know, for instance, that none of the tests modifies anything.

Meanwhile, while a missing break can be a brutal error in a language that allows it, it's usually trivial to set up linting to require either an explicit break or a comment indicating fallthrough.

Re: The Performance Impact of C++'s `final` Keyword

#346
post #304

Earlier quoted context omitted.

In my opinion, the only things that really matter are algorithmic complexity and readability. And even algorithmic complexity is usually only an issue a certain scales. Whether or not an 'if' is faster than a 'switch' is the micro of micro optimizations -- you better have a good reason to care. The question I would have for you is was your bunch of ifs more readable than a switch would be.

... really matter are algorithmic complexity ... This is not entirely true either... Measure. There are many cases where the optimiser will vectorise a certian algorithm but not another... In many cases On^2 vectorised may be significantly faster than On or Onlogn even for very large datasets depending on your data... Make your algorithms generic and it won't matter which one you use, if you find that one is slower s…

While you are not wrong, if you have a decent language you will discover all the useful algorithms are already in your standard library and so it isn't a worry. Your code should mostly look like apply this existing algorithm to some new data structure.

Re: The Performance Impact of C++'s `final` Keyword

#347
post #57

Earlier quoted context omitted.

Yup. That said, the linear test is often faster due to CPU caches, which is why JITs will often convert switches to if/elses. IMO, switch is clearer in general and potentially faster (at very least the same speed) so it should be preferred when dealing with 3+ if/elseif statements.

Hard disagree that it's "clearer". I have had to deal with a ton of bugs with people trying to be clever with the `break` logic, or forgetting to put `break` in there at all. if statements are dumber, and maybe arguably uglier, but I feel like they're also more clear, and people don't try and be clever with them.

I always set -Werror=implicit-fallthrough, among others. That prevents fallthrough unless explicitly annotated. Sadly these will forever remain optional warnings requiring specific compiler flags, since requiring them could break compiling broken legacy code.

Re: The Performance Impact of C++'s `final` Keyword

#348
post #287

Earlier quoted context omitted.

All OOP languages have it, the issue is related to changing the behaviour of the base class, and the change introducing unforceen consequences on the inheritance tree. Changing an existing method way of calling (regular, virtual, static), changing visibility, overloading, introducing a name that clashes downstream, introducing a virtual destructor, making a data member non-copyable,...

> All OOP languages have it, the issue is related to changing the behaviour of the base class, and the change introducing unforceen consequences on the inheritance tree. C++ largely solves it by having tight encapsulation. As long as you don't change anything that breaks your existing interface, you should be good. And your interface is opt-in, including public members and virtual functions.

Not when you change the contents of the class itself for public and protected inheritance members, which is exactly the whole issue of fragile base class.

It doesn't go away just because private members exist as possible language feature.

Re: The Performance Impact of C++'s `final` Keyword

#349

Earlier quoted context omitted.

> What final enables is devirtualization in certain cases. The main advantage of devirtualization is that it is necessary for inlining. I think that enabling inlining is just one of the indirect consequences of devirtualization, and perhaps one that is largely irrelevant for performance improvements. The whole point of devirtualization is eliminating the need to resort to pointer dereferencing when calling virtual me…

An extra indirection (indirect call versus direct call) is practically nothing on modern hardware. Branch predictors are insanely good, and this isn't something you generally have to worry about. Inlining is by far the most impactful optimization here, because it can eliminate the call altogether, and thus specialize the called function to the callsite, lifting constants, hoisting loop variables, etc.

Vfuncs are only fast when they can be predicted: https://forwardscattering.org/post/28

Re: The Performance Impact of C++'s `final` Keyword

#350
post #348

Earlier quoted context omitted.

> All OOP languages have it, the issue is related to changing the behaviour of the base class, and the change introducing unforceen consequences on the inheritance tree. C++ largely solves it by having tight encapsulation. As long as you don't change anything that breaks your existing interface, you should be good. And your interface is opt-in, including public members and virtual functions.

Not when you change the contents of the class itself for public and protected inheritance members, which is exactly the whole issue of fragile base class. It doesn't go away just because private members exist as possible language feature.

That's not a fragile base, that's just a fragile class. You can break APIs for all kinds of users, including derived classes.

Some APIs are aimed towards derived classes, like protected members and virtual functions, but that doesn't make the issue fundamentally different. It's just breaking APIs.

Point is, in C++ you have to opt-in to make these API surfaces, they are not the default.

Post reply on HN