Live data from Hacker News

The Sad State of Debug Performance in C++

vittorioromeo.info

71–80 of 111 posts

Re: The Sad State of Debug Performance in C++

#71
post #2

> Even if -Og was ubiquitous, it is still suboptimal to -O0: it can still inline code a bit too aggressively for an effective debugging session. IMO, the problem is not so much the inlining, but the sad state of the optimization passes losing track of many things and producing useless debug info.

It's not just inlining. With -Og (clang or gcc), the debugger frequently can't even tell me the value of function arguments because they've been "optimized out".

Am I alone in always debugging in release builds? If you get a crash dump from prod, it'll be a release build. Or if your reproducer takes 2 minutes of (release build!) setup time, you don't want to go through that in debug builds.

Yes, the debugger might have "forgot" the value of that function argument. So check a few other levels of the call stack that it was passed through. Or a local variable might be unavailable in a given line. So have breakpoints a bit before, or trace it into the next function call.

And when all else fails, you can go and look at the disassembly. Yes, calling conventions are stupid, instruction mnemonics are a hassle and register aliases are terrible, but even with rudimentary understanding you can generally figure out what's going on. If your code does a multiplication, look for the `mult`. If your code calls some external function, look for a `call`. Compilers can do cool things with your code, but computers are not magic - you can actually just look at what your CPU does, and it will strongly correlate with your code. As an added bonus, this helps you actually understand what code your compiler produces, which (if you reach for C++!) you probably should care about to some degree.

The only reason I have to care about debug builds is code coverage and/or iterator-checked runs, but that's something that can run overnight if it has to.

/rant

Re: The Sad State of Debug Performance in C++

#72

Earlier quoted context omitted.

std::move is not only about performance. It is about being able to disallow destructive copies of certain types. I.e. unique_ptr can not be copied, and auto_ptr can. Can you write perfectly functioning code without these features? Sure! You just have to be accurate. Same with performance. Where nowadays simple std::move() on a return value suffices you had previously explicitly to pass the object/vector with which yo…

Note that you don't even have to std::move a return value in almost all cases. (The very much non-language-lawyer explanation is that local variables are automatically moved from when you return them.)

Also, if you write

> return std::move(ret);

it can interfere with return value optimisation (RVO). There might be cases where the move makes sense but to my understanding it's enough to just use return.

It's very much possible to take advantage of modern C++ move semantics without an explicit call to move. Maybe you return a unique_ptr created by make_unique in a factory method, or call swap() on a moveable object, etc. I rarely need to write std::move to take advantage of modern C++ although it took some self-education about rvalues etc to learn how to do it.

Re: The Sad State of Debug Performance in C++

#73

Earlier quoted context omitted.

> Presumably my code has been less than optimally efficient or something Yep, it was either less efficient than it could be, or more clunky, or incorrect. I remember back when boost was still a thing, everyone using boost::shared_ptr indiscriminately. Refcounting has a very nontrivial cost to it if implemented correctly. Things like rvalue refs and std::move allow to properly implement the concept of "ownership trans…

I've been using boost::shared_ptr for 22+ years. My code has no concept of ownership transfer, ever. The reason for shared_ptr in our codebase is not to move ownership around, but to ensure correct lifetimes. For examples, objects in the model cannot die until the view has dropped references to them.

Yup, that's bad. Unless you actually have a complicated ownership scheme or shared state across multiple threads, unique ownership is preferable on all accounts. At the point where

1) your object graph is complex enough to warrant the ubiquitous use of shared pointers;

2) you can afford the performance hit

it's better to use an actual garbage collector, even if it's bolted-on unreal engine-style.

With shared_ptr-style ref counting, every dtor is potentially a bomb waiting to go off causing a cascading effect. It's actually less predictable than a GC that you can run manually at fixed intervals or whenever a convenient opportunity arises.

Re: The Sad State of Debug Performance in C++

#74

I've been using C++ since 1993. I still don't understand how I've written all this code and never once found myself using, or wishing I could use std::move. Presumably my code has been less than optimally efficient or something, because it seems that most people talking about "modern" C++ view it as absolutely central to the language.

> I've been using C++ since 1993. I still don't understand how I've written all this code and never once found myself using, or wishing I could use std::move.

No offense, but are you using modern C++? One of the most common uses of std::move is around ownership transfer of std::unique_ptr - most moderately big projects will have those. Other uses are around preventing copies, etc but those need more thought since you may actually cause regressions by getting in the compiler's way.

Re: The Sad State of Debug Performance in C++

#75
post #21

The problem is that C++ is full of so called "zero-cost-abstractions" that are only zero cost if you ignore: 1. Compile time 2. Non-optimized builds Stuffing everything into the Standard Library using lots of template magic does has its upside, but debug performance is one of the big downsides. Another one are really clunky interfaces, like std::variant. This should really have been a language feature, not a library…

Compile time and non-optimized builds aren't what you want to consider when you care about code that's going to run across thousands of cores or ultra low latency work - those are the cases where you should be considering C++ in the first place.

Re: The Sad State of Debug Performance in C++

#76

Earlier quoted context omitted.

std::move is not only about performance. It is about being able to disallow destructive copies of certain types. I.e. unique_ptr can not be copied, and auto_ptr can. Can you write perfectly functioning code without these features? Sure! You just have to be accurate. Same with performance. Where nowadays simple std::move() on a return value suffices you had previously explicitly to pass the object/vector with which yo…

Note that you don't even have to std::move a return value in almost all cases. (The very much non-language-lawyer explanation is that local variables are automatically moved from when you return them.)

As u/dryanau says returning std::move breaks the return value optimization such that clang at least will warn about it by default.

Re: The Sad State of Debug Performance in C++

#77
post #62

Earlier quoted context omitted.

Just want to remind you that `-Og` in Clang is not representative here, as it's exactly the same as `-O1` -- they implemented support for the flag just to ease transitions between GCC and Clang without having to filter out flags, but it doesn't provide any debugging-specific advantage.

Yes, those who think -Og is no good because they are clang users should perhaps file bug reports or contribute improvements instead.

[deleted]

Re: The Sad State of Debug Performance in C++

#78
post #24
post #21

The problem is that C++ is full of so called "zero-cost-abstractions" that are only zero cost if you ignore: 1. Compile time 2. Non-optimized builds Stuffing everything into the Standard Library using lots of template magic does has its upside, but debug performance is one of the big downsides. Another one are really clunky interfaces, like std::variant. This should really have been a language feature, not a library…

The phrase "zero-cost abstraction" is a lie anyway. It's all relative to how much compiler optimization happens. If you actually executed the C++ specification step by step, the code would be dog slow, because there are so many implicit conversions, constructions, inlining, constant folding, etc. We could quibble about how much static resolution qualifies (template expansion, overload resolution, etc--that's executin…

If you implemented any language following the spec in a step by step manner they would be dog slow, because any usable spec has to be very explicit in every step required for every operation.

All those conversions that you believe only happen if you implement the spec “step by step” always happen.

Actual implementations of languages then implement the explicitly described semantics, in whatever way they feel is most efficient.

Re: The Sad State of Debug Performance in C++

#79

Earlier quoted context omitted.

I've been using boost::shared_ptr for 22+ years. My code has no concept of ownership transfer, ever. The reason for shared_ptr in our codebase is not to move ownership around, but to ensure correct lifetimes. For examples, objects in the model cannot die until the view has dropped references to them.

Yup, that's bad. Unless you actually have a complicated ownership scheme or shared state across multiple threads, unique ownership is preferable on all accounts. At the point where 1) your object graph is complex enough to warrant the ubiquitous use of shared pointers; 2) you can afford the performance hit it's better to use an actual garbage collector, even if it's bolted-on unreal engine-style. With shared_ptr-styl…

>It's actually less predictable than a GC that you can run manually at fixed intervals or whenever a convenient opportunity arises.

Not really. It's difficult to reason about, but it's entirely deterministic, unlike a tracing GC.

Post reply on HN