Live data from Hacker News

The Sad State of Debug Performance in C++

vittorioromeo.info

101–110 of 111 posts

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

#101

Earlier quoted context omitted.

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 unavailabl…

You're not. Debug builds are rarely that useful and tbh I don't understand why is there such a big fuss about it. The most difficult and most time consuming problems found in production code can only be debugged and, well, understood and hence fixed in production (optimized) code only.

That said, under assumption you have a good code coverage with tests, and your code uses a lot of asserts to check pre- and post-conditions, debug builds can be useful for early detection of broken invariants.

I get that the development cycle is shortened through shorter compile times but that is simply the artifact of all optimizing compilers and not something which is exclusive to C++. And luckily this can be easily alleviated.

Expecting to have comparable runtime performance (which one is it?) in languages backed with optimizing compilers seem a bit delusional to me and even more so if you're in the realm of soft real-time systems.

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

#102
post #19

I'm with Kernigham and Pike: the debugger is for getting a stack trace out of core dumps. Printf is for debugging.

I didn't expect something as silly as this coming out of Kernighams mouth

You should read "the practice of programming", he (well mostly Rob Pike if I understand the relationship) has some well reasoned criticism of the ways people use debuggers. In fact it is not silly - debuggers often make people feel more productive than they really are, so if you use a debugger (which I do once in a while) be careful to be objective about them.

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

#103

Earlier quoted context omitted.

when you cross a certain level of complexity, the deterministic nature becomes a Turing tarpit of sorts: yes, it's "deterministic", but the number of factors affecting the behavior is so large and their interactions so complex that it might as well be "non-deterministic" -- it's impractical to reason about in detail.

I'll take the "almost non-deterministic" over the non-deterministic. Computers are very good at reasoning about complex deterministic systems.

...and a garbage collector is one of those. It's not magic fairy dust, it's still software that works on the same processing unit as your program, and obeys the same rules. Computers are finite automata, they can't beget true randomness, so any software system that is isolated to a single machine is "deterministic", if you have enough context.

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

#104

Earlier quoted context omitted.

I'll take the "almost non-deterministic" over the non-deterministic. Computers are very good at reasoning about complex deterministic systems.

...and a garbage collector is one of those. It's not magic fairy dust, it's still software that works on the same processing unit as your program, and obeys the same rules. Computers are finite automata, they can't beget true randomness, so any software system that is isolated to a single machine is "deterministic", if you have enough context.

> Computers are finite automata, they can't beget true randomness

Plenty of peripheral inputs that can be used to induce true randomness.

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

#105
This dovetails with something I was thinking about recently in the context of Reproducible Builds (or the lack thereof). For most of its history Computer Science (at least as practiced by industry) has gone all-in on "functionally equivalent" transformations, and done almost nothing in the space of "output invariant" guarantees.

In my day job we sometimes we need to reproduce the build of a firmware ROM or executable, sometimes decades after the engineer who last built it left. Getting a match is easier (or even possible) for older tech only because of the relative unsophistication of the compilers used - even then it's only reproducible "by accident," and not because the compiler vendor made any guarantees about X language construct reliably produces Y machine code and data layout.

But we need that! For getting accurate baselines. For security verification. And there's no reason in principle we should have to forego updating compilers, IDEs, and OS environments in the indirect hope of not disturbing anything. Those are two separate things: if we had a through-line of higher level language construct -> semantically defined transformation (irregardless of optimization settings) -> machine code, vendors could continue to update their IDEs and compilers while just making sure they still respect the invariants.

C++'s so-called zero-cost abstractions are poor substitute for this: header (library) writers and C++ gurus write code as if they worked like this "guaranteed output transform" I describe, but no compiler actually has to respect it (nevermind that the fine details of what the transformation actually is isn't nailed down) and it differs between Debug and Release build which is particularly bad for game development as TFA makes clear.

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

#106

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.

Your comment strikes at a deeper issue than what I suspect most replies it gets will address. The example of your never having needed nor missed std::move in all the code you've written rests on the distinction between specific uses and generality.

The reason modern C++ has become what it has become (in both good and bad senses) is because template generic programming has led it down a path of built-in constructs needing to consider an ever-growing zoo of weird cases. For every simple answer to the question, "how should this thing work?" there's a counter-example, "what if the type doesn't have that property?"

That's where we get things like std::move, etc. By contrast when dealing with your own code you can use knowledge of what is actually going to be passed to ignore many of these concerns or change your abstraction-using code to have or not have some property.

Overall, simplifying the use-case or restricting the domain is more pragmatic than making things fully general. C++, in its standard library / template utility headers is painted into a corner in that respect since it doesn't control the use-case, and the effort to restrict the domain (i.e. concepts) was delayed as well as representing itself another layer of complexity rather than a pure simplification.

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

#107

Earlier quoted context omitted.

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

Well, "modern" is relative. We just recently moved to C++11 :) We do not use unique_ptr and to be honest I can find little to no use for it anywhere in our 600k lines of C++.

With apologies to Oscar Wilde: I spent all morning putting in std::unique_ptr and all afternoon taking it out again.

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

#108

Earlier quoted context omitted.

I'll take the "almost non-deterministic" over the non-deterministic. Computers are very good at reasoning about complex deterministic systems.

...and a garbage collector is one of those. It's not magic fairy dust, it's still software that works on the same processing unit as your program, and obeys the same rules. Computers are finite automata, they can't beget true randomness, so any software system that is isolated to a single machine is "deterministic", if you have enough context.

>Computers are finite automata, they can't beget true randomness

"Non-deterministic" in this context means that the behavior of the program is not deducible from the state of the current thread of execution, or more generally, from the state of the system being analyzed. If another thread modifies the current thread's memory, that modification is non-deterministic because you couldn't have foreseen it by following any pointer that's reachable by the current thread. If a cosmic ray hits a computer chip and flips a bit, that bitflip is non-deterministic; you couldn't have predicted that that bit would be flipped by looking at any part of the computer.

In other words, an effect is non-determistic with respect to a causal chain if it's causally unrelated to it.

A tracing GC is non-deterministic for two reasons. First, it causes effects (namely, pauses) in all threads, even those that never allocate any memory. Second, it makes the lifetime of objects unpredictable. Thread A can allocate an object and then drop it, and when that object will be destroyed depends on the behavior of the entire system, not just thread A's. RAII doesn't exist in GC'd languages (except through using or try-finally hacks).

Reference counting is completely deterministic. If a thread holds the last reference to an object you can predict with certainty when that object will be released and which objects will also be released as a consequence, before actually releasing the object. You don't need to know what any other thread is doing to make this prediction.

Crucially, if you find that sometimes a reference counting program spends too long releasing an object graph, all you need to do to debug it is to run the program again under the same conditions and break when it reaches that point. Try doing the same with a garbage collector.

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

#109

Earlier quoted context omitted.

...and a garbage collector is one of those. It's not magic fairy dust, it's still software that works on the same processing unit as your program, and obeys the same rules. Computers are finite automata, they can't beget true randomness, so any software system that is isolated to a single machine is "deterministic", if you have enough context.

> Computers are finite automata, they can't beget true randomness Plenty of peripheral inputs that can be used to induce true randomness.

...of which the computer is not the source.

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

#110

Earlier quoted context omitted.

...and a garbage collector is one of those. It's not magic fairy dust, it's still software that works on the same processing unit as your program, and obeys the same rules. Computers are finite automata, they can't beget true randomness, so any software system that is isolated to a single machine is "deterministic", if you have enough context.

>Computers are finite automata, they can't beget true randomness "Non-deterministic" in this context means that the behavior of the program is not deducible from the state of the current thread of execution, or more generally, from the state of the system being analyzed. If another thread modifies the current thread's memory, that modification is non-deterministic because you couldn't have foreseen it by following an…

> Reference counting is completely deterministic. If a thread holds the last reference to an object you can predict with certainty when that object will be released and which objects will also be released as a consequence, before actually releasing the object. You don't need to know what any other thread is doing to make this prediction.

You're contradicting your own definition of determinism here. Other threads may be holding references to the same object, and unless you know what the other threads are doing, you _cannot_ predict when an object is going to be released just by looking at the execution of a single thread.

You need to look at the overall behavior of the application, with the complex object graph spanning multiple threads.

> all you need to do to debug it is to run the program again under the same conditions

this is much easier said than done.

Post reply on HN