Live data from Hacker News

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

16bpp.net

351–360 of 385 posts

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

#351
post #52

Earlier quoted context omitted.

This is one of the cases where JIT compiling can shine. You can use a bazillion interfaces to decouple application code, and the JIT will optimize the calls after it found out which implementation is used. This works as long as there is only one or two of them actually active at runtime.

You don't need a JIT to do whole program optimization.

AOT whole program optimization has two limits:

* It is possible with `dlopen()` to load code objects that violate the assumptions made during compilation.

* The presence of runtime configuration mechanisms and application input can make it impossible to anticipate things like the choice of implementations of an interface.

One can always strive to reduce such situations, but it might simply not be necessary if a JIT is present.

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

#353

Earlier quoted context omitted.

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.

"is practically nothing on modern hardware" if the data is already present in the L2 cache. Random RAM access that stalls execution is expensive. My guess is this is why he didn't see any speedup: all the code could fit inside the L2 cache, so he did not have to pay for RAM access for the deference. The number of different classes is important, not the number of objects as they have the same small number of vtable po…

Firefox has done a lot of work on devirtualization over the years. There is a cost.

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

#354
post #66

Earlier quoted context omitted.

While I personally find the if statements harder to immediately mentally parse/grok--as I have to prove to myself that they are all using the same variable and are all chained correctly in a way that is visually obvious for the switch statement--I don't find "but what if we use a naive compiler" at all a useful argument to make as, well, we aren't using a naive compiler, and, if we were, there are a ton of other thin…

Per my sibling comment, I think the argument is not about speed, but simplicity. Awkward switch syntax aside, the switch is simpler to reason about. Fundamentally we should strive to keep our code simple to understand and verify, not worry about compiler optimizations (on the first pass).

Right, and there I would say we even agree, per my first sentence; however, I wanted to reply not to you, but to doctor_phil, who was explicitly disagreeing about speed.

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

#355
post #304

Earlier quoted context omitted.

... 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.

I don't disagree with you at all on this. However you may need to combine several to get to an end result. And if that happens a few times in a codebase, well makes sense to factor that into a library.

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

#356
post #348

Earlier quoted context omitted.

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.

I give up, word games to avoid acknowledging the same happens.

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

#357

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…

> Devirtualization helps performance because you are able to benefit from inheritance and not have to pay a performance penalty for that. Without the final keyword, a performance oriented project would need to be architected to not use inheritance at all, or in the very least in code in the hot path, because that sneaks gratuitous pointer dereferences all over the place, which require running extra operations and has…

Maybe a nitpick, but virtual inheritance is a term used for something else entirely.

What you're talking about is dynamic dispatch

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

#358

Earlier quoted context omitted.

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…

No. Dynamic PGO was first introduced in .NET 6 but was not mature and needed two releases worth of work to become enabled by default. It needs no user input and is similar to what OpenJDK Hotspot has been doing for some time and then a little more. It also is required for major features that were strictly not available previously: guarded devirtualization of virtual and interface calls and delegate inlining. Also, II…

Sure. But static PGO was introduced in .Net Framework 4.7.0. And we're talking about apps in production, so there's no excuse NOT to use static PGO on the .net framework 4.7.0 version.

And you have misrepresented the contents of the blogs. The projects discussed in the blogs are typically claiming ~30% improvements (perhaps because they weren't using static PGO in their 4.7.0 incarnation), with two dramatic outliers that seem to be related to migrating from IIS to Kestrel.

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

#359
post #127

Earlier quoted context omitted.

How do you even get meaningful profiling out of most modern langs? It seems the vast majority of time and calls gets spent inside tiny anonymous functions, GC allocations, and stuff like that.

This is easy in most modern programming languages. JVM ecosystem has IntelliJ Idea profiler and similar advanced tools (AFAIK). .NET has VS/Rider/dotnet-trace profilers (they are very detailed) to produce flamegraphs. Then there are native profilers which can work with any AOT compiled language that produces canonically symbolicated binaries: Rust, C#/F#(AOT mode), Go, Swift, C++, etc. For example, you can do `samply…

I mean sure, but I've never seen much in a flamegraph besides noise.

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

#360
post #2

What final enables is devirtualization in certain cases. The main advantage of devirtualization is that it is necessary for inlining. Inlining has other requirements as well -- LTO pretty much covers it. The article doesn't have sufficient data to tell whether the testcase is built in such a way that any of these optimizations can happen or is beneficial.

> 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…

> In classes with larger inheritance chains, you can easily have more than one pointer dereferencing taking place before you call a virtual members function.

This is not a thing in C++; vtables are flat, not nested. Function pointers are always 1 dereference away.

Post reply on HN