Live data from Hacker News

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

16bpp.net

21–30 of 385 posts

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

#21
post #18

tldr: sprinkled a keyword around in the hopes that it "does something" to speed things up, tested it, got noisy results but no miraculous speedup. I started skimming this article after a while, because it seemed to be going into the weeds of performance comparison without ever backing up to look at what the change might be doing. Which meant that I couldn't tell if I was going to be looking at the usual random noise…

This is what I was waiting for too. Especially with the large regression on Clang/Ubuntu. Maybe he uncovered a Clang/LLVM codegen bug, but you’d need to compare the generated assembly to know.

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

#22
post #16
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.

If you already have LTO, can't the compiler determine this information for devirtualization purposes on its own?

If your runtime environment has dynamic linking, then the LTO pass can't always be sure that a subclass won't be introduced later that overrides the method.

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

#23

What should be evaluated is removing indirection and tightly packing your data. I'm sure you'll gain a better performance improvement. virtual calls and shared_ptr are littered in the codebase. In this way: you can avoid the need for the `final` keyword and do the optimization the keyword enables (de-virtualize calls). >Yes, it is very hacky and I am disgusted by this myself. I would never do this in an actual produc…

Macros that are giving you some value can be ok. In this case, once the performance conclusion is reached, the only reason to continue using a macro is if you really need the `final`ity to vary between builds. Otherwise, just delete it or use the actual keyword.

(But I'm worse than the author; if I'm just comparing performance, I'd probably put `final` everywhere applicable and then do separate compiles with `-Dfinal=` and `-Dfinal=final`... I'd be making the assumption that it's something I either always or never want eventually, though.)

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

#24
It's difficult to discuss this stuff because the impact can be negligible or negative for one person, but large and consistently positive for another. You can only usefully discuss it on a given baseline, and for something like final I would hope that baseline would be a project that already enjoys PGO, LTO, and BOLT.

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

#25
post #6

You should use final to express design intent. In fact I’d rather it were the default in C++, and there was some sort of an opposite (‘derivable’?) keyword instead, but that ship has sailed long time ago. Any measurable negative perf impact should be filed as a bug and fixed.

> In fact I’d rather it were the default in C++, and there was some sort of an opposite (‘derivable’?) keyword instead

Kotlin (which uses the equivalent of the Java "final" keyword by default) uses the "open" keyword for that purpose.

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

#26
I don't do much C++, but I have definitely found that engineers will just assert that something is "faster" without any evidence to back that up.

Quick example, I got in an argument with someone a few years ago that claimed in C# that a `switch` was better than an `if(x==1) elseif(x==2)...` because switch was "faster" and rejected my PR. I mentioned that that doesn't appear to be true, we went back and forth until I did a compile-then-decompile of a minimal test with equality-based-ifs, and showed that the compiler actually converts equality-based-ifs to `switch` behind the scenes. The guy accepted my PR after that.

But there's tons of this stuff like this in CS, and I kind of blame professors for a lot of it [1]. A large part of becoming a decent engineer [2] for me was learning to stop trusting what professors taught me in college. Most of what they said was fine, but you can't assume that; what they tell you could be out of date, or simply never correct to begin with, and as far as I can tell you have to always test these things.

It doesn't help that a lot of these "it's faster" arguments are often reductive because they only are faster in extremely minimal tests. Sometimes a microbenchmark will show that something is faster, and there's value in that, but I think it's important that that can also be a small percentage of the total program; compilers are obscenely good at optimizing nowadays, it can be difficult to determine when something will be optimized, and your assertion that something is "faster" might not actually be true in a non-trivial program.

This is why I don't really like doing any kind of major optimizations before the program actually works. I try to keep the program in a reasonable Big-O and I try and minimize network calls cuz of latency, but I don't bother with any kind of micro-optimizations in the first draft. I don't mess with bitwise, I don't concern myself on which version of a particular data structure is a millisecond faster, I don't focus too much on whether I can get away with a smaller sized float, etc. Once I know that the program is correct, then I benchmark to see if any kind of micro-optimizations will actually matter, and often they really don't.

[1] That includes me up to about a year ago.

[2] At least I like to pretend I am.

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

#27
post #20
post #3

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

I believe the wording I’ve seen is that compilers may not respect the inline keyword, not that it is ignored.

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

#28
post #16
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.

If you already have LTO, can't the compiler determine this information for devirtualization purposes on its own?

MSVC with LTO and PGO will inline virtual calls in some situations along with a check for the expected vtable, bypassing the inlined code and calling the virtual function normally if it is an unexpected value.

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

#29
post #3

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

What if I dlopen a shared object that contains a derived class, then instantiate it. You cannot statically verify that I won't. Or you could swap out a normally linked shared object for one that creates a subclass. Etc etc. This kind of stuff is why I think shared object boundaries should be limited to the lowest common denominator (basically c abi). Dynamic linking high level languages was a mistake. The only winning move is not to play.

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

#30
post #16
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.

If you already have LTO, can't the compiler determine this information for devirtualization purposes on its own?

not if there is a shared libray or other plugin. Then you coannot determine until runtime if there is an override.
Post reply on HN