

The Hidden Cost of C++ - groby_b
http://www.rachelslabnotes.com/2009/10/the-hidden-cost-of-c/

======
dkarl
The argument boils down to the fact that C++ can express more in a single line
of code than C can.

 _a = func(b,c);_

....

 _Is it a function call, a member function call, or is it an anonymous
constructor? Are b and c implicitly invoking copy constructors for other
classes as part of type coercion? Is that a normal assignment, or an
assignment operator? Is there a cast operator involved?_

If it's such a complicated expression, then the equivalent C code would be
correspondingly large and hard to understand, and it would be just as
complicated to figure out its cost. The not-so-hidden cost of C is that even
simple things end up being many, many lines of code. C++ was invented because
certain kinds of C programs -- programs that were _already being written in C_
\-- were painfully verbose to express and complicated to change.

If writing your code in C would be simple and clear, then you would have to be
stupid to write it as complex, obscure C++. When your C++ code gets complex
and you start banging your head against a wall, imagine re-expressing it in C.
If the result would be an improvement, then you screwed up. If you blame C++
for screwing up, then you're a language feature junkie. Admit it and check
yourself into rehab.

 _That is the hidden cost. The mental model for a simple function call became
incredibly large and complex, and every function call is potentially as
complex. Which makes reasoning about performance a rather hard thing to do....
All that translates ultimately into either worse performance or longer
development time. Neither one is something you like to hear about._

I don't know how it is a "hidden cost," because it's quite well understood
that any reasonably expressive language can say more in a single line of code
than C. Anyway, the size of the mental model of your _program_ is what you
should be worried about. C++ doesn't give you worse performance or longer
development time. It gives you more choices for how you express your program.
It means more _different_ problems, because you can write C-style code and
have C-style problems, or you can use the possibilities C++ offers and have
different problems. If you already know that the choices forced on you by C
are usually the right ones for your domain, then by all means apply that
knowledge to how you write C++. For instance:

 _Worse, it makes profiling harder than necessary. All the type coercions that
happen at the API level will show up as separate functions, not attributed to
the callee, but the caller._

If you want them attributed to the callee, it's pretty simple. Do the
coercions in the callee, just like you would have in C.

~~~
groby_b
Your counterargument boils down to "use C instead". That is exactly what I am
currently advocating.

I don't blame C++ for screwing up - I blame it for being an ill-designed
language that makes it _easy_ to screw up.

Any of the high-level features of C++ are well-implemented in any number of
decent languages that don't obfuscate your code and incur horrible link times.
And my argument (for game development) is that C++ is indeed the wrong
language for the domain. In fact, I'd argue it's wrong for most, if not all
domains. And I'm not exactly alone - I can't recall any prominent figure that
actually thinks C++ is a decent language, except Bjarne Stroustrup. (Correct
me if I'm wrong - I'd love to hear about it!)

<blockquote> If you want them attributed to the callee, it's pretty simple. Do
the coercions in the callee, just like you would have in C. </blockquote>

The issue is that in many instances, I own the caller, but not the callee. And
the person owning the callee can _unintentionally_ make my code perform worse
by simply changing the API. Or adding a destructor.

~~~
dkarl
No, my counterargument is to use the features of C++ when they help and not
when they hurt. I claim that C++ doesn't hurt C programmers. It just brings
out their deficiencies in different ways. For instance, embracing features you
don't understand and then complaining about the consequences is stupid, and
stupid people don't write good code in any language. You understand C++ well
enough to know the pros and cons (or at least the cons) of various C++
language features, but you complain that your fellow programmers might not:

 _The issue is that in many instances, I own the caller, but not the callee.
And the person owning the callee can unintentionally make my code perform
worse by simply changing the API. Or adding a destructor._

This is just bad programming practice on their part. The performance cost of a
destructor shouldn't be a surprise to the person who writes it. If someone is
messing with the performance of types you use or removing functions(+) you
call without consulting you and without themselves taking responsibility for
calls to that functionality, then obscure features of C++ is just one of the
many ways they're going to screw you on a regular basis. How would those
programmers screw you in C? I don't know, but I know they would.

Nobody who knows C++ well enough to use it effectively would call it a
"decent" language in the sense of "not obscene," but it is definitely "decent"
in the sense of "adequate." It's easiest to define C++'s deficiencies with
respect to other languages (for instance, memory management in C++ is much
more intrusive in source code than in Java) but comparing it to C is way too
simple. After all, the primary problem with C++ is that it doesn't remove any
of C's dangers; it just gives you better ways of abstracting them away in some
cases. The second problem with C++ is that it's extremely complicated and
takes a long time to learn. And that's it. Every other supposed misfeature of
C++ (relative to C) seems to stem from people hitting the second problem
without realizing it. In your case, your coworkers should be more conservative
about messing with things (such as defining expensive automatic type
conversion functions) when they don't understand the consequences (such as
those type conversions actually being invoked.)

(+) This is the most plausible explanation for different type conversions
suddenly being invoked. It could also happen by adding a function that invokes
a more expensive type conversion to a type that is more closely related to the
type declared by the caller, thus making the new function a better match than
the old one, but it's unlikely that a conversion between two types that are
more closely related would actually be more expensive.

~~~
groby_b
_No, my counterargument is to use the features of C++ when they help and not
when they hurt._

That was Stroustrups reasoning: "Only pay the cost when you use it". In
retrospect, believe that that's a bad choice for language design, because
there's a good chance somebody will use a feature without fully understanding
the ramifications it has over the entire code base.

C opts for the opposite and uses annotation to treat certain code segments as
"special" and make them faster - "inline", and (in the distant past)
"register" come to mind.

 _(or at least the cons)_

I believe I do get the pros as well, at least to some extent. I've been using
it since CFront came out ;) And I'd still advocate it for use in a small team
(<5 people?) of experienced programmers - you _can_ make it sing.

But gamedev engineering teams are at least 15+ people, with the occasionally
less-experienced ones thrown into the mix. C++ is, in a sense, like a
professional power tool. It sure can get stuff done, but the wrong person uses
it and its a blood bath, and it's not appropriate for single small home
repairs. (Gah. Bad analogy, but I can't come up with a better one right now)

 _This is just bad programming practice on their part. The performance cost of
a destructor shouldn't be a surprise to the person who writes it_

Possibly. I'd argue that it's hard to always keep all performance
ramifications in mind. But let's for a moment say it's indeed simply bad
programming practice - the issue that makes this a problem is that _my_ code
pays the cost, not the offending code. Which is a rather indirect.

C++ changes often have tendrils all through the system. It is easy to make
inadvertent mistakes.

* but it is definitely "decent" in the sense of "adequate.*

It clearly gets the job done, yes. We are shipping games occasionally, after
all. I'm looking for better ways to ship games, and I think that abandoning
C++ might bring gains.

 _The second problem with C++ is that it's extremely complicated and takes a
long time to learn._

That's what my hidden cost is referring to - C++'s complexity makes it hard to
understand. You certainly _can_ write code that performs well in C++, but it
is arguably harder to get it right than C.

I like to think there's a better solution than either one hiding somewhere,
but I haven't found it yet.

------
JoeAltmaier
I fixed a single line of code that drove our embedded processor to 50% cpu -
it called 5 ctor/dtors. Changing it to a ref arg with a ref return, and
dropped to under 10%. And yes, class defs had to be redeclared, the line of
code was unchanged. Very indirect, utterly beyond the original authors
comprehension.

~~~
hazzen
Do you know how to fix almost every one of these cases for good? Just don't
allow implicit copying of your classes. Problem solved. If someone tries to
write that horrible code, they can't.

This is more a question of knowing what you are doing and less of C++ sucking.
C doesn't solve this - you still have to know what you are doing there or you
end up shooting yourself in the foot. I much prefer having an interface with
virtual methods to a struct with function pointers.

~~~
JoeAltmaier
Of course! However the original author of the offending code didn't know to
define private X(X&), so had the problem. C++ is a power saw; you can cut off
your hand.

------
icefox
And the hidden cost of using Java is that there is a JVM! These are only
hidden to developers who don't really know C++. If you have virtual functions
everywhere and have api that encourages copy or anonymous constructors you get
what you asked for. And any profiler worth its salt should be able to show you
what is really happening so you can quickly fix it.

For a game shop who is worried about performance there are many little things
you can have your developers do to prevent the compiler from doing many of
these things and you should have it be part of your normal API reviews to make
sure that developers follow them. A simple example

class Box { public: Box(int x); };

class Foo { public: Foo(Box a) };

... Foo(1); ...

Pop Quiz: How would you change Foo (or the whole api) to cause Foo(1) to cause
an error?

~~~
kmavm
Pop Quiz: Why should I clutter my brain with these little guessing games about
what the compiler is doing? And why should these pop quizzes slow down my
group's code reviews? There are, after all, languages out there that do not
force this insanity on us.

The alternative for performance-critical applications is not Java, but C. C
remains surprisingly tough competition after all these years for those rare
pieces of software where programmer time is cheap compared to hardware
resources. Having had non-trivial experience of both languages, _reasoning_
about the performance of a C program is much, much easier than reasoning about
non-trivial C++ programs' performance. Guesses based on local code inspection
have an order of magnitude less uncertainty attached to them, which is
basically what Rachel is saying here.

~~~
blub
I am surprised to see that so many programmers still like to reason and guess
what the performance of their program is instead of profiling it...

And hearing C proposed as an alternative to C++ is mind boggling. I don't want
to match malloc/free calls, strcat strings together or realloc pointers. I
don't want to declare a bunch o function pointers in a struct and call it OOP.
I don't want to use macros because C99 has poor adoption rate and C89 doesn't
have inline. I don't want to watch the compiler blissfully convert unrelated
types to one another just because it can. I don't want to pass types around as
void* and lose any useful type information I might have had. I don't want to
fumble with return codes.

And in C++ I don't have to. I can use Qt, the STL and Boost and be very
productive. Telling me to program in C is not even funny.

~~~
groby_b
I propose a _lower_ level than C++ for the kernel, and a _higher_ level for
the rest.

C++ is simply at the wrong abstraction level.

Oh, and I don't _want_ to manually track resources myself, either. But guess
what, somebody has to do. And on a game console, that's you. There's not much
of an OS to speak of.

You did notice I was talking about game development, right? ;)

~~~
blub
My reply was not targeted at you and I don't do game programming.

Anyway:

* smart pointers don't need an OS.

* for me, C++ is at the exactly right abstraction level to allow me to use only C++ instead of "C++ + other language" or "other language and C for speed". Most C + HLL proponents underestimate the logistics overhead of using multiple programming languages.

~~~
groby_b
_Most C + HLL proponents underestimate the logistics overhead of using
multiple programming languages._

I'll concede that as a valid concern, especially for smaller projects where
everybody touches many areas of the code. As projects grow, a separation
becomes easier.

Add to that the fact that most games need scripting support anyways, and
you'll see that the logistics overhead is at least not much different.

------
st3fan
Wow it is C++ bashing season again it seems.

Yes you have to know what you are doing in C++. But this is not different in
other languages. Understanding and optimizing code is a touch discipline. No
matter what language.

There is also another side to this story. C++ can absolutely generate highly
efficient code. Code that performs much better than C equivalents.

For example, I've looked a lot at generated (iPhone/ARM) code for cases where
(function) templates are used and C++ wins most of the time. Doing compile-
time configuration of code is usually much more efficient than having runtime-
configured code. By using templates and inline functions the compiler can do
some seriously cool performance tricks.

Sire, there may be more code (bigger text segment) but that is a small
sacrifice for what you gain in speed. Specially these days where a 8MB fart
app is easily downloaded over a 3G connection without hesitation.

~~~
groby_b
Yes, you _can_ use C++ to do amazing things. In large-scale projects, that
usually backfires, though, unless you contain the "magic", because
_interactions_ in C++ are harder to predict.

The problem with C++ is not that you have to know what you are doing - you
also have to know what everybody else is doing.

And the "small sacrifice" of a bigger text segment is a big one in this
particular domain. We only have 512M available, and a tremendous chunk of that
is eaten up by graphics. An executable seriously exceeding ~20MB would spell
trouble.

And I'd argue that AAA games are slightly more complex than a fart app ;)

~~~
psyklic
When I code in a team, we don't just write one big mass of C++ and _hope_ that
all the interactions work out okay!

Instead, we follow modular programming practices. If some interactions are too
slow, we know who didn't optimize his section and we kick the problem back to
him.

~~~
groby_b
That would be nice. Except that - again, for performance reasons - we often
have to break the boundaries between modules. Most game engines are a tangled
web, since many parts are talking to many other parts. (I think there are ways
around that, and there will certainly be a post on it. But that's the way
things work right now)

So it's not always that easy to just find a guilty party and blame them.

------
psyklic
The author is worried about _optimizing_. He should know that in high-
performance applications, you should just write the code _then_ profile it. We
all know that C++ is fast; the real question the author should be asking is
whether C or C++ can more quickly express ideas/is more maintainable/etc.

This post about not knowing how a function is called is really splitting
hairs, bordering on irrelevant imo -- sure, it can affect the time taken _to
call a function_ by ten times, but this is not a large performance hit at all
for 95% of your code, and even less since most high performance items are on
the GPU. If it ends up that it does affect performance in a critical area
(found via profiling), the author even suggested how it can easily be fixed.

I don't see what the big deal is. If you're a game programmer, change your
coding conventions so that you don't get into problems like this.

~~~
groby_b
She does know that you need to profile, TYVM. But ideally, you pay at least
cursory attention to performance even when you write the code. Writing code
with complete disregard to performance, saying "oh, the profiler will find it"
is not an acceptable choice.

You don't write sloppy code and argue that you can find bugs in the debugger,
either, do you?

And no, most high-performance items are not on the GPU. Most games are already
GPU-bound, so you want to take load off of it. (Also, with the arrival or LRB
et al., the GPU/CPU distinction becomes moot)

If those issues were irrelevant, I wouldn't care about them. There's one thing
I _do_ care about: Shipping high quality games, developed as fast as possible.
C++ hurts that goal.

And no, the 95%/5% rule does not hold true in game development. But don't
trust me - read, for example, Tim Sweeny, the guy behind the Unreal engine.
According to him, there are not hotspots. Another Unreal programmer:
<http://lambda-the-ultimate.org/node/1277#comment-14238>

"Dan Vogel, was fond of pointing out that UE2 had a basically flat profile"

 _That_ is the big deal. In a flat profile, things like calling overhead
matter everywhere.

~~~
psyklic
Worrying about not knowing how functions are called on your first iteration is
such a moot point. Simple programming conventions (e.g. no multiple
inheritance) can eliminate this problem. Even if each function call does take
10x longer, I would argue that in 99% of the cases you wouldn't even notice
the difference, practically speaking.

 _Shipping high quality games, developed as fast as possible._

If this is what you care about, then you should care about optimizing AFTER
you write the initial version and identify hot spots.

 _You don't write sloppy code and argue that you can find bugs in the
debugger, either, do you?_

Sloppy code vs. "slow" code are vastly different. I would much much rather
have slow code, since 99% of the time it isn't even noticed by the user.
Hence, it was worth not thinking about how to improve it.

 _most high-performance items are not on the GPU._

I agree that you want to take load off the GPU. However, CPU speed has not
been the limiting factor in games for a LONG time. In fact, in conferences
such as the GDC it is rarely discussed any longer, only in the context of
using more CPU if available for non-engine tasks such as AI.

 _And no, the 95%/5% rule does not hold true in game development._

There is a big difference between what is essentially a graphics engine (you
reference the Unreal engine) and a video game. If you look at modern games, do
you think it is a waste that they are based on interpreted scripting
languages? They can do this because as I said above, CPU isn't as limiting a
factor as before.

~~~
groby_b
_Sloppy code vs. "slow" code are vastly different._

Not really. Some amount of up-front investment can avoid a lot of pain at the
end. Do I obsess about every single call? No, of course not - but I try to
avoid obvious performance issues.

While you _can_ optimize them after the fact, it's more time you're spending.

_CPU speed has not been the limiting factor in games for a LONG time_

Yes, actually, it is. It might not be _raw_ speed - we're talking cache misses
instead - but you still have to be careful with your resource usage.

_There is a big difference between what is essentially a graphics engine (you
reference the Unreal engine) and a video game_

Yes, and no. Unreal is more than a "graphics" engine - it's an entire game
engine. (There's way more than graphics there.) And CPU performance still is
discussed at GDC. See here, for example:
[http://www.scribd.com/doc/15118967/Hitting-60Hz-in-Unreal-
En...](http://www.scribd.com/doc/15118967/Hitting-60Hz-in-Unreal-Engine)

And the games built on top of it use some scripting. And often dive into C++
and even the engine itself to make the game actually perform well.

But that's exactly the model I'm advocating. Write the kernel in a low-level
language (preferrably not C++, for many reasons, some of them mentioned in my
post), and do the game logic in a scripting layer on top.

The faster you can get the kernel, the more resources you can devote to a
language that makes life much easier for the rest of the world.

So yes, I wasn't exact. When saying "game development", I was referring to
"engine development". The point still stands - I think C++ is inappropriate
for low-level tasks (too much "magic") as well as high-level tasks.
(Incredibly convoluted language. No, I'm not saying Stroustrup is a bad
designer. I'm saying the constraints he imposed on himself forced C++ to head
that direction. And design by committee helped.)

 _If you look at modern games,_

I do. Every day. Intimately. I work on them. That's why this topic matters to
me. It'd be kind of stupid to complain about C++ usage in game development
without knowing about it ;)

------
acg
It always surprises me that some will seek to defend C++ when even its author
seems to admit that it is flawed. Many famous programmers have pointed to the
language's limitations. People use it because of pragmatism: it has what they
need for the APIs they want to use.

 _If you think C++ is not overly complicated, just what is a protected
abstract virtual base pure virtual private destructor and when was the last
time you needed one?_ Tom Cargill

------
huhtenberg
There are constructors in C++. The water is wet, how sneaky of it.

You either know the code you are working with or you don't. C++ or not.

~~~
uriel
What he is pointing out is that C++ makes it harder to 'know the code you are
working with'.

~~~
huhtenberg
I understand the point, and I am saying it is a trivial one.

Calling func(x) in C may as well be causing x to be cloned or have some other
nasty side effects that are not immediately obvious from looking at the code.
Similarly how one should always keep in mind the existence of macros when
reading C code, one should also keep in mind the existence of constructors and
casting operators with C++. That's hardly the hidden cost.

It is easier to write messy or obscure code in C++, especially for a novice
programmer. But this has less to do with the language per se and more with
established coding practices within specific team. If there _is_ a hidden cost
to C++, it is the fact that it requires higher level of competence. Not that
it has constructors.

~~~
groby_b
The point is that, with a rational team, I can tell (roughly) what a function
is doing, based on the name. It's not going to be called 'dot_product' and
clone data passed in ;)

What I can't tell is how much extra code the compiler will generate at this
point, and that is the problem.

> If there is a hidden cost to C++, it is the fact that it requires higher
> level of competence. Not that it has constructors.

I didn't say the cost is in the constructors. They're a _symptom_ of the cost.
The cost is creating the mental model of your app.

Sure, if the entire team consisted of C++ superstars, we _might_ be able to
get away with this. Given the team size on current games, (and the fact that
superstars are rare) that's unlikely to be the case.

~~~
nuserame
If there's no enough "superstars", then perhaps your coding guidelines should
accommodate that so that the "mental model" of the app would be more
managaeble for the code monkeys.

~~~
groby_b
This is not a question of "code monkeys". It's simply a question of having
some programmers that are less experienced than others. And that's kind of
unavoidable if you ever want to hire new people...

