Earlier quoted context omitted.
See this is why I find this odd. Is there a theory as to how devirtualisation could hurt performance?
Jumps/calls are actually be pretty cheap with modern branch predictors. Even indirect calls through vtables, which is the opposite of most programmers intuition. And if the devirtualisation leads to inlining, that results in code bloat which can lower performance though more instruction cache misses, which are not cheap. Inlining is actually pretty evil. It almost always speeds things up for microbenchmarks, as such…
The Performance Impact of C++'s `final` Keyword
261–270 of 385 posts
Re: The Performance Impact of C++'s `final` Keyword
#262Also, now that I think of it, they should have run the code under perf and compared the stats.
Re: The Performance Impact of C++'s `final` Keyword
#263It's is an insane level of ignorance about how these things are decided by the standards committee.
Re: The Performance Impact of C++'s `final` Keyword
#264What 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.
See this is why I find this odd. Is there a theory as to how devirtualisation could hurt performance?
Re: The Performance Impact of C++'s `final` Keyword
#265Earlier quoted context omitted.
doesn't the compiler usually do well enough that you really only need to worry about time critical sections of code? Even then you could go in and look at the assembler and see if it's being inlined, no?
I find that gcc and clang are so aggressive about inlining that it's usually more effective to tell them what not to inline. In a moderately-sized codebase I regularly work on, I use __attribute__((noinline)) nearly ten times as often as __attribute__((always_inline)). And I use __attribute__((cold)) even more than noinline. So yeah, I can kind of see why someone would say inlining is 'evil', though I think it's more…
When writing ultra-robust code that has to survive every vaguely plausible contingency in a graceful way, the code is littered with code paths that only exist for astronomically improbable situations. The branch predictor can figure this out but the compiler frequently cannot without explicit instructions to not pollute the i-cache.
Re: The Performance Impact of C++'s `final` Keyword
#266Earlier 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…
With a Roslyn-based compiler at work I saw 20 % perf improvement just by switching from .NET Core 3.1 to .NET 6. No idea how slow .NET Framework was, though. I probably can't target the code to that anymore.
But for regex even with precompilation, the compiler got a lot better at transforming the regex into an equivalent regex that performs better (automatic atomic grouping to reduce unnecessary backtracking when it's statically known that backtracking won't create more matches for example) and it also benefits a lot from the various vectorized implementations of Index of, etc. Typically with each improvement of one of those core methods for searching stuff in memory there's a corresponding change that uses them in regex.
So where in .NET Framework a regex might walk through a whole string character by character multiple times with backtracking it might be replaced with effectively an EndsWith and LastIndexOfAny call in newer versions.
Re: The Performance Impact of C++'s `final` Keyword
#267I'm surprised that it has any impact on performance at all, and I'd love to see the codegen differences between the applications. Mostly the `final` keyword serves as a compile-time assertion. The compiler (sometimes linker) is perfectly capable of seeing that a class has no derived classes, but what `final` assures is that if you attempt to derive from such a class, you will raise a compile-time error. This is simil…
"inline" is confusing in C++, as it is not really about inlining. Its purpose is to allow multiple definitions of the same function. It is useful when you have a function defined in a header file, because if included in several source files, it will be present in multiple object files, and without "inline" the linker will complain of multiple definitions. It is also an optimization hint, but AFAIK, modern compiler ig…
Traditionally you'd use `static` for that use case, wouldn't you?
After all, `inline` can be ignored, `static` can't.
Re: The Performance Impact of C++'s `final` Keyword
#268I would say the most performance impact would give `constexpr` followed by `const`. I wouldn't bet any money on `final` which in C++ is a guard of inheritance, and C++ function invocation address is resolved the `vtable` hence final wouldn't change anything. Maybe the author was mistaken with `final` keyword in Java
In my experience the compiler is pretty good at figuring out what is constant so adding const is more documentation for humans, especially in C++, where const is more of a hint than a hard boundary. Devirtualization, as can happen when you add a final, or the optimizations enabled by adding a restrict to a pointer, are on the other hand often essential for performance in hot code.
In the same TU, sure. But across TU boundaries the compiler really can't figure out what should be const and what should not, so `const` in parameter or return values allows the compiler to tell the human "You are attempting to make a modification to a value that some other TU put into RO memory.", or issue similar diagnostics.
Re: The Performance Impact of C++'s `final` Keyword
#269Earlier quoted context omitted.
Jumps/calls are actually be pretty cheap with modern branch predictors. Even indirect calls through vtables, which is the opposite of most programmers intuition. And if the devirtualisation leads to inlining, that results in code bloat which can lower performance though more instruction cache misses, which are not cheap. Inlining is actually pretty evil. It almost always speeds things up for microbenchmarks, as such…
Another for the pro side: inlining can allow for better branch prediction if the different call sites would tend to drive different code paths in the function.
The branch predictors actually hash the history of the last few branches taken into the branch prediction query. So the exact same branch within a child function will map different branch predictors entries depending on which parent function it was called from, and there is no benifit to inlining.
It also means that branch predictor can also learn correlations between branches within a function. Like when a branches at the top and bottom of functions share conditions, or have inverted conditions.
Re: The Performance Impact of C++'s `final` Keyword
#270Earlier quoted context omitted.
Yeah the practical cases for devirtualization are when you have a base class, a derived class that you actually use, and another derived class that you use in tests. For your release binary the tests aren't visible so that can all be devirtualized. In cases where you have Dog and Goose that both derive from Animal and then you have std::vector , what is the compiler supposed to do?
The compiler simply knows that the actual dynamic type is Animal because it is not a pointer. You need Animal* to trigger all the fun virtual dispatch stuff.