Live data from Hacker News

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

16bpp.net

241–250 of 385 posts

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

#241
post #209

Earlier quoted context omitted.

> (perhaps one of the inlining heuristics is "Don't inline a function with more than 100 tokens", and the "final" keyword pushes a couple of functions to 101). That definitely is one of the heuristics in MSVC++. We have some performance critical code and at one point we noticed a slowdown of around ~4% in a couple of our performance tests. I investigated but the only change to that code base involved fixing up an err…

Since the inlining is performed in MSVC's backend, as opposed to its frontend, and hence operates strictly on MSVC's intermediate representation which lacks information about tokens or the AST, it's unlikely due to tokens. std::exception does not take a string in its constructor, so most likely you used std::runtime_error. std::runtime_error has a pretty complex constructor if you pass into it a long string. If it's…

> std::exception does not take a string in its constructor

You're right, I used it as a short-hand for our internal exception function, forgetting that the std one does not take a string. Our error handling function is a simple static function that takes an std::string and throws a newly constructed object with that string as a field.

But yes, it could very well have been that the string surpassed the short string optimisation threshold or something similar. I did verify the assembly before and after and the function definitely inlined before and no longer inlined after. Moving the 'throw' (and, importantly, the string literal) into a separate function that was called from the same spot ensured it inlined again and the performance was back to normal.

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

#242
post #209

Earlier quoted context omitted.

> (perhaps one of the inlining heuristics is "Don't inline a function with more than 100 tokens", and the "final" keyword pushes a couple of functions to 101). That definitely is one of the heuristics in MSVC++. We have some performance critical code and at one point we noticed a slowdown of around ~4% in a couple of our performance tests. I investigated but the only change to that code base involved fixing up an err…

Since the inlining is performed in MSVC's backend, as opposed to its frontend, and hence operates strictly on MSVC's intermediate representation which lacks information about tokens or the AST, it's unlikely due to tokens. std::exception does not take a string in its constructor, so most likely you used std::runtime_error. std::runtime_error has a pretty complex constructor if you pass into it a long string. If it's…

Wow, I had no idea. And I thought I knew about most of C++'s weirdnesses.

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

#243
post #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…

> `if(x==1) elseif(x==2)...` because switch was "faster" and rejected my PR

Yeah, that's never been true. Old compilers would often compile a switch to __slower__ code because they'd tend to always go to a jump table implementation.

A better reason to use the switch is because it's better style in C-like languages. Using an if statement for that sort of thing looks like Python; it makes the code harder to maintain.

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

#244
Mildly related programming language trivia:

Fortran has virtual functions ("type bound procedures"), and supports a NON_OVERRIDABLE attribute on them that is basically "final". (FINAL exists but means something else.). But it also has a means for localizing the non-overridable property.

If a type bound procedure is declared in a module, and is PRIVATE, then overrides in subtypes ("extended derived types") work as usual for subtypes in the same module, but can't be affected by overrides that appear in other modules. This allows a compiler to notice when a type has no subtypes in the same module, and basically infer that it is non-overridable locally, and thus resolve calls at compilation time.

Or it would, if compilers implemented this feature correctly. It's not well described in the standard, and only half of the Fortran compilers in the wild actually support it. So like too many things in the Fortran world, it might be useful, but it's not portable.

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

#245
I think it was Chandler Carruth who said "If you're not measuring, then you don't care about performance." I agree, and by that measure, nobody I've ever worked with cares about performance.

The best I'll see is somebody who cooked up a naive microbenchmark to show that style 1 takes fewer wall nanoseconds than style 2 on his laptop.

People I've worked with don't use profilers, claiming that they can't trust it. Really they just can't be bothered to run it and interpret the output.

The truth is, most of us don't write C++ because of performance; we write C++ because that's the language the code is written in.

The performance gained by different C++ techniques seldom matters, and when it does you have to measure. Profiler reports almost always surprise me the first few times -- your mental model of what's going on and what matters is probably wrong.

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

#246

I think it was Chandler Carruth who said "If you're not measuring, then you don't care about performance." I agree, and by that measure, nobody I've ever worked with cares about performance. The best I'll see is somebody who cooked up a naive microbenchmark to show that style 1 takes fewer wall nanoseconds than style 2 on his laptop. People I've worked with don't use profilers, claiming that they can't trust it. Real…

It matters to some degree. If it's just a simple technique you can file away and repeat as muscle memory, well that means your code is that much better.

From a user perspective it could be the difference between software that's pleasant to use and software that's annoying to use. From a philosophical perspective it's the difference between software that functions vs software that works well.

Of course it depends on your context as to whether this is valued, but I wouldn't dismiss it. Once person's micro-optimization is another person's polish.

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

#248

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…

What if you add -fwhole-program-vtables on clang?

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

#249

Earlier quoted context omitted.

A significant part of it is that what engineers believe was effectively true at one time. They simply haven't revisited those beliefs or verified their relevance in a long time. It isn't a terrible heuristic for life in general to assume that what worked ten years ago will work today. The rate at which the equilibriums shift due to changes in hardware and software environments when designing for system performance is…

.NET is a particularly bad case for this because it was a decade of few performance improvements, which caused a certain intuition to develop within the industry, then 6-8 years of significant changes each year (with most wins compressed to the last 4 years or so). Companies moving from .NET Framework 4.6/7/8 to .NET 8 experience a 10x average performance improvement, which naturally comes with rendering a lot of per…

.NET 4.6 to .NET 8 is a 10x "average" performance improvement. I find this hard to believe. In what scenarios? I tried to Google for it and found very little hard evidence.

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

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

> "inline" is confusing in C++, as it is not really about inlining. Its purpose is to allow multiple definitions of the same function.

No, its purpose was and is still to specify a preference for inlining. The C++ standard itself says this:

> The inline specifier indicates to the implementation that inline substitution of the function body at the point of call is to be preferred to the usual function call mechanism.

https://eel.is/c++draft/dcl.inline

Post reply on HN