Live data from Hacker News

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

16bpp.net

361–370 of 385 posts

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

#361
post #324

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.

I had a section of code which incurred ~20 clock cycles to make a function call to a virtual function in a critical loop. That's over and above potential delays resulting from cache misses and the need to place multiple parameters on the stack. I was going to eliminate polymorphism altogether for this object but later figured out how to refactor so that this particular call could be called once a millisecond. Then if…

Could just be inefficient spilling caused by ABI requirements due to the inability to inline.

In general if you're manipulating values that fit into registers and work on a platform with a shitty ABI,you need to be very careful of what your function call boundaries look like.

The most obvious example is SIMD programming on Windows x86 32-bit.

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

#362

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.

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

Same as any other branch. They're fast if predicted correctly and slow if not.

If they cannot be predicted, write your code accordingly.

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

#363

Earlier quoted context omitted.

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…

It’s a moot point. Almost no one used static PGO and its feature set was way more limited - it did not have devirtualization which provides the biggest wins. Though you are welcome to disagree it won’t change the reality of the impact .NET 8 release had on real world code.

It’s also convenient to ignore the rest of the content at the links but it seems you’re more interested in proving your argument so the data I provided doesn’t matter.

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

#364
post #359

Earlier quoted context omitted.

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.

My experience is complete opposite. You just need to construct a realistic load test for the code and the bottlenecks will stand out (more often than not).

Also there is learning curve to grouping and aggregating data.

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

#365
post #302
post #36

Earlier quoted context omitted.

In general the compiler/linker cannot assume that derived classes won't arrive later through a shared object. You can tell it "I won't do that" though with additional flags, like Clang's -fwhole-program-vtables, and even then it's not that simple. There was an effort in Clang to better support whole program devirtualization, but I haven't been following what kind of progress has been made: https://groups.google.com/g…

This optimization option isn't on by default? That sounds like a lot of missed optimization. Most programs aren't going to be loading from shared libraries. Maybe I can set this option at work. Though it's scary because I'd have to be certain.

Optimization means "make it faster without changing behaviour in ways I don't like". Clang can't generally default that one to on because it doesn't know whether you're going to splice in more code it can't see at runtime.

Lots of code gets slower if it might need to be called from something not currently in the compiler's scope. That's essentially what ABI overhead is. If there isn't already, there should be a compiler flag that says "this is the whole program, have at it" which implies the vtables option.

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

#366

Earlier quoted context omitted.

At the level that LLVM's LTO operates, no information about classes or objects is left, so LLVM itself can't really devirtualize C++ methods in most cases

You appear to be correct. Clang does not devirtualize in LTO, but GCC does. Personally I consider this very strange. $ cat animal.h cat.cpp main.cpp // animal.h #pragma once class animal { public: virtual ~animal() {} virtual void speak() = 0; }; animal& get_mystery_animal(); // cat.cpp #include "animal.h" #include class cat final : public animal { public: ~cat() override{} void speak() override{ puts("meow"); } }; s…

I think this is a bug. There's dedicated metadata that's supposed to end up on the indirect call to list the possible targets and when that list of possible targets is this short it should be turning into a switch over concrete targets. Don't have time to dig into the IR now but it might be worth posting to the github llvm issues.

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

#367

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.

C++ vtables need 2 levels of indirection. See the asm or decompile it with ghidra. First the vtable field, and then the method field.

Of course you have to worry about pointer chasing, when you can easily avoid it. Either via a switch to a single indirection (by passing method pointers around) or inlining with final. Or other compile-time specialization.

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

#368
post #299

Earlier quoted context omitted.

How does const affects code generation in C/C++? Last time I checked, const was purely informational. Compilers can't eliminate reads for const pointer data, because const_cast exists. Compilers can't eliminate double calls to const methods, because inside function definition such functions can still legally modify mutable variables (and have many side effects). What actually may help is __attribute__((pure)) and __a…

Const affects code generation when used on variables . If you have a `const int i` then the compiler can assume that i never changes. But you're right that this does not hold true for const pointers or references. > What actually may help is __attribute__((pure)) and __attribute__((const)), but I don't see them often in real code (unfortunately). It's disppointing that these haven't been standardized. I'd prefer diff…

Do you have an example where a const on a variable changes codegen? I would be surprised if the compiler couldn't figure out variable constness itself.

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

#369
post #299

Earlier quoted context omitted.

How does const affects code generation in C/C++? Last time I checked, const was purely informational. Compilers can't eliminate reads for const pointer data, because const_cast exists. Compilers can't eliminate double calls to const methods, because inside function definition such functions can still legally modify mutable variables (and have many side effects). What actually may help is __attribute__((pure)) and __a…

Const affects code generation when used on variables . If you have a `const int i` then the compiler can assume that i never changes. But you're right that this does not hold true for const pointers or references. > What actually may help is __attribute__((pure)) and __attribute__((const)), but I don't see them often in real code (unfortunately). It's disppointing that these haven't been standardized. I'd prefer diff…

> If you have a `const int i` then the compiler can assume that i never changes.

Plus, you can’t even compile your code if you try to modify a const variable.

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

#370

Earlier quoted context omitted.

Const affects code generation when used on variables . If you have a `const int i` then the compiler can assume that i never changes. But you're right that this does not hold true for const pointers or references. > What actually may help is __attribute__((pure)) and __attribute__((const)), but I don't see them often in real code (unfortunately). It's disppointing that these haven't been standardized. I'd prefer diff…

Do you have an example where a const on a variable changes codegen? I would be surprised if the compiler couldn't figure out variable constness itself.

If you modify a const variable, compiler will error out and refuse to compile.
Post reply on HN