I don't necessarily disagree with this assessment but you can easily create functionality in C that requires looking at multiple source files to understand whats going on.
x = foo(bar(), baz());
Now you have to look at the definitions for bar and baz to understand the cost of this line.
I guess this is the trade-off with using a high level language. Expressivity vs transparency.
What's especially nice is that TRUE is defined as -1 in that source, not 1, which could easily confuse someone who isn't intimately familiar with C's boolean operators and conditional expressions (since something like if((a < b) == TRUE) { ... } would actually be incorrect).
Even with that in mind, it would still be nice to have a "safer to use" systems language than C++ generally available. Perhaps Go or D can fill that role (though good luck making that happen for game development on Windows...)
Lest I come across as grousing without contributing to the discussion, let's take a peek at the author's main point on hidden costs.
Most of the argument revolves around objects. There are two ways you can run into "hidden" surprises with objects in C++ that the author is pointing out. One involves failing to use the language features given, the other involves writing some seriously odd code.
class C {
public:
/*explicit*/ C( int ); // Adding explicit avoids hidden costs
// This combination (or something like it) is lethal.
C( int, double );
operator bool() const;
};
So for the author's code to have hidden performance concerns, they would need a constructor taking two parameters of the proper type and an automatic conversion operator that converts to a type assignable as their result. Basically, the stars have to align right and you have to knowingly write some dangerous code.
Don't get me wrong, you can hide costs pretty easily in C++ and write some atrocious code. But usually this issue is alleviated by following basic best practices. The hard part is knowing what those best practices are.
Making the copy constructor and operator= private (see boost::noncopyable to take care some of that boilerplate) takes care of some of the other hidden costs.
I do agree that C++ is a complex and hodgepodge language, but I am not sure there are acceptable alternatives yet. It would be great if JVM had native support for value types, i.e., a way to use manual memory management, bypass bounds checks, or call native code without incurring serialization costs.
Yes, I am aware that C# does this (and I like C# and F# a great deal as languages), but a) I am not deploying Windows in production b) Mono is not yet a viable high performance server side platform (it does look promising for desktop and mobile application or for simpler ASP.NET webapps).
C is a beautiful and simple language (one I have strong affinity for) but manually implementing vtables is a bit painful (see a large pure C project for an example of that), nor is there any support for type-safe generic programming (which is an issue I have with Go as well -- even though I fully understand their reasons for omitting generics).
I feel like--in 2012--claiming C++ has 'hidden' deficiencies/costs/complexities/problems requires a willful ignorance of the last 20 odd years of people C++ bashing.
True in a way. C++ programmers like to think that they're writing efficient code because of the language they're using. The same applies to some C programmers (although at a lesser extent I think). In fact, the biggest gains are made on an algorithmic level. That's something a lot of people like to forget.
Then again, the reverse isn't true. There are languages (or more precisely their implementations) that just are slow.
The biggest gains are often at the algorithmic level, but once you are using a reasonable algorithm there are often large gains to be had in your bottlenecks.
Furthermore, good asymptotic complexity doesn't always correlate with faster code for values of n you actually encounter. Linear search is much faster than binary search, because of cache behavior, up to a surprisingly large n on many architectures.
> the biggest gains are made on an algorithmic level
I have had a few experiences of simply porting something rather directly to C++ and seeing 10X speed and memory improvements. The 'hidden costs' in other languages are loads of unavoidable heap allocations and pointer indirection that really don't happen with C++. And C++ compilers do a ton of very smart optimization these days.
With the generic programming constructs and static polymorphism approaches in C++ you can often get really big performance increases over what's practical in C or Java. std::sort in C++, for example, has often been found a few times faster than stdlib.h qsort. It's because of compile time optimizations that languages like C and Java can't do.
Just a nitpick: sorting is a bad example because qsort could be made just as fast if it were defined in the header and marked inline. C++ provides more flexibility (you don't have to make everything inline, you can have a family of "real" functions indexed by type), but it isn't really necessary for sorting.
Eh, you're right in theory, but in practice not really.
There's on the order of a 1,000-1,000,000x difference or more between a Numpy version of what OkCupid does, and what their C++ does, even if you use the same algorithm.
Similarly, when we recently farmed out a string similarity algorithm from Python to Java, with a more expensive and sophisticated approach in the latter, it ended up being conservatively ~10,000x faster.
I love Python and use it for...just about everything really, but until you've actually optimized software at a systems level you really have no idea what you're talking about.
Proper algorithms and data structures are necessary but not sufficient.
It's a pity higher-level languages don't offer better escape hatches when you need them.
Your ad hominem attack is totally uncalled for. I've been using C++ for over 20 years and what she says is completely obvious to me.
The mental effort you have to make to determine the overhead of an expression by looking at the call site is greater in C++ than it is in C.
That's an undeniable consequence of features like operator overloading, constructors, destructors, virtual functions, etc. It has nothing to do with how well you understand the language. The information just isn't there at the call site for you to see.
The question is a different one. It's whether that fact is compensated for by the power and reduced mental effort these features enable elsewhere. Constructors and destructors make RAII possible. That arguably makes memory management a lot easier than in C.
The mental effort you have to make to determine the overhead of an expression by looking at the call site is greater in C++ than it is in C.
Isn't this just a specific example of saying "the mental effort you have to make to determine the overhead of an expression by looking at the call site is greater in higher-level languages than in lower-level ones"?
It's true, precisely because you are using higher-level abstractions. I'd be more interested in seeing an example where this wasn't true.
The only good reason to use C++ is because you (1) need a level of abstraction that C doesn't give you, (2) need level of performance that no garbage collected language can give you (even compiled ones), and (3) can't even use C + Lua (or some other scripting language), probably because you need the abstraction and the performance in the same place.
(This is an awfully narrow application domain, compared to what C++ is actually used for)
At that point, the performance characteristics of your programs have become part of the specs. You do not want to hide them under the carpet like you would do with a garbage collected language. Yet C++ does. It wouldn't hurt if it syntactically distinguished initializations, casts, calls by reference and so on.
There's some truth in that, but I don't think "higher level" and "lower level" is sufficiently well defined to make that as automatically true as you present it. For instance, languages that allow you to redefine the assignment operator are not necessarily higher level than languages that don't.
Or take reference parameters. In C++, if you pass an object to a function you cannot know by looking at the call site if the object is going to be copied or not. The parameter could be defined as a reference, but you have to look at the function definition to find out. In C#, in Java, in Python and in C you know it by looking at the call site. These other languages are all over the place in terms of any high/low level categorization.
No, sorry. There are a bunch of factors that make the point a little more complicated.
If you're writing, let's say, scenegraph traversal, it's really tempting to get it working on the PPU and then port it to run on an SPU. A vtable dereference to main memory will crash your SPU job and it is not always obvious why. Obviously, you can't use static analysis tools to make sure the right instructions are transferred, so you have to do it by hand. A modern PS3 game has on the order of low hundreds f different SPU jobs...not much fun. Even discounting that, SPUs will work best when fed branchless parallelized jobs, I wouldn't be surprised to see an order of magnitude difference between a vtable call on a pointer array and a static call on an object array.
Keeping pointers to objects in a polymorphic array is a games performance anti-pattern because of the dereferencing cost, but it's necessary to call virtual functions...there's a hidden cost right there.
Which loop is faster, by eyeballing, and by how much?
void load(Assets* a) {
for (int j=0; j<m_numAssets; j++) {
loadAsset(a[j]);
m_numLoadedAssets++;
}
}
void load(Assets* a) {
int numLoadedAssets=0;
for (int j=0; j<m_numAssets; j++) {
loadAsset(a[j]);
numLoadedAssets++;
}
m_numLoadedAssets = numLoadedAssets;
}
I've seen the former style run literally 1000 times slower than the latter...that's obvious? I submit in a world of out-of-order processors it is not at all.
I don't think that all the author's points are spot on...Koenig lookup is Byzantine but IDEs do a good job of it, ditto source-level reasoning about dispatch. The underlying theme, that C++ is not a good fit for modern game development, shouldn't be so trivially dismissed.
m_numLoadedAssets is a member of some unnamed class the snipet of code is extracted from. When you call the method, it is likely deeper in the stack than any local variable, or even in the heap.
It depends where *this is allocated. In the worst case, it is allocated in main memory while you wanted to stay in the graphic memory, or something.
A naive compiler would then access memory (or the cache) instead of using registers. A Sufficiently Advanced Compiler would guess that calling ++ many times is the same as incrementing in one go, and hoist that out of the loop, but apparently this one is a bit cruder.
Now the same could be said about m_numAssets, but this one isn't written to, so the compiler only have to put a copy in a register, which I guess is a simpler optimization to do.
To answer your question, that particular situation would be better in any language that forces you to explicit the reference to "this" (or "self"). Imagine how we could modify C++:
void member_function() {
int local_variable++;
local_variable++; // This is okay
member_variable++; // That should not be allowed
this->member_variable++; // This should be written instead
}
Applied to the example in the GGP above:
void load(Assets* a) {
for (int j=0; j<this->m_numAssets; j++) {
loadAsset(a[j]);
this->m_numLoadedAssets++;
}
}
We see that every non-local access is prefixed by something ("this->" and "a[" here). The heavier syntax suggests a heavier cost, so the programmer will more easily think of hoisting those out of the loop, if possible (either manually or through compiler optimizations).
Any language that gave you more explicit insight into cache spilling, pipeline stalls, DMA wait etc. would be better. Maybe the solution there is better tools, but if you've ever written a C++ parser you might agree that C++ tooling is a language issue. :-)
No, the performance problem I'm referring to is the Load Hit Store problem, where the pipeline is constantly stalled (in-order execution) waiting for the CPU to write to L1 cache.
This is more by chance than anything, but I think my correction addresses precisely that: the point is not to hoist the increment out of the loop. The point is to hoist the member access. That way, you end up with only one write to memory, instead of a write and a read. (There is another member access in the condition of the loop, but I guess the compiler can optimize it.)
I explained that here: http://news.ycombinator.com/item?id=4540107 I did not get the Load Hit Store problem, but I did sense there was a problem with the repeated read access to memory.
By the way, it looks like your problem is a bit more subtle than the textbook Load Hit Store: you don't read the location you just wrote to. I guess the write access dirties more than one word (at least a cache line, maybe more).
Still they're both effectively right about it. "Object oriented niceness" comes at a cost. With C++ it might be smaller than in dynamic languages like Python, where things like function call overhead can be ridiculously big, but it's still there.
I think they are both wrong about it. What the argument boils down to is that the limitations of C prevent you from using things that might lead you astray. That's not an argument for or against C++ it's just an argument against large languages in general and an argument for programmer discipline in specific. It might work for Linus if he reimplements C++ features in a better way but it does not help all users and doesn't help all the time. Do we honestly believe a beginning programmer will be better off reimplementing std::vector or just using a fixed array instead?
If I remember correctly somewhere further down in that particular Linus rant he was saying that you can look at a call site and tell what's going on, the same argument the poster here is making, but do we honestly believe Linus never uses function pointers? And the argument about the name of the function giving us a rough estimate of the overhead does not sound valid to me.
As a C programmer and former C++ programmer I can see a valid argument hidden in these types of posts but I think we'd be better off with a little less attitude and a little more useful guidelines.
> Do we honestly believe a beginning programmer will be better off reimplementing std::vector or just using a fixed array instead?
I believe a beginning programmer will be best off using fixed arrays at first, then reimplementing a basic version of std::vector when the need for it arises (which it inevitably will) and then use whatever vector datatype his chosen language offers him. This will give the programmer an understanding of what is actually going on and why the array is suddenly resizeable/resizes itself. That's the sort of attention to detail that separates a good programmer from a great one.
> do we honestly believe Linus never uses function pointers
Actually, they're all over the place in the kernel. They're used all the time when writing device drivers. I use them myself in every bit of quasi-polymorphic C code I write (although I'm not a kernel hacker). I think they're quite nifty actually although they can obscure things. Here we come back to how we're using constructs a language gives us. Used correctly, specifying well documented interfaces, they give birth to hardly any problems. Executed the wrong way, they give birth to all sorts of bugs and weird behaviour and obscure the code.
> the argument about the name of the function giving us a rough estimate of the overhead does not sound valid to me.
It only will if you know the function, yes. Otherwise it's a weird argument.
I think your opinion is just as valid as Linus' is. I think, larger languages open the flood gates for decisions which can come at a cost for performance or maintainability. Programmers should be aware of that and useful guidelines would definitely help.
Obviously, yes. One can't expect jumps across multiple levels of libraries, wrappers and APIs to be free of cost. That's what Carmack referred to as "20 layers of crap". He's effectively right about it. On the other hand we can't reinvent the wheel everything we write a line of code.
One of the most important aspects of balancing productivity with performance is knowing which parts of code are crucial to performance and should be written with that in mind. That's one of the toughest parts as well. Usually one is better off profiling than guessing but I believe an educated guess can go a long way nevertheless.
That's true and the thing with C++ is that its philosophy is "if you don't use a feature, you're not getting performance hit associated with it". Be it anonymous functions or virtual calls.
This also works as an argument against basically any language more complicated than C, which -- since we're not all writing everything in C all the time -- should suggest that although such languages may have "hidden costs", they also bear significant benefits.
With modern optimizing compilers and CPU architectures, you can't really predict the cost of code merely by looking at it anyways. Not in any language.
Will that function call be inlined? Does it have side-effects? Are the arguments constant, so that it might even be evaluated at compile-time? Can it be hoisted out of a loop? Do its pointer args alias, preventing many of these operations? Is it big enough to blow the cache, or will it all fit in L1 and benefit from really fast memory accesses? Can branch-prediction operate effectively on its loops, or does it have an unpredictable pattern that will flush the pipeline many times?
I've heard that the best way to get your program to run fast is to get it into a compiler benchmark suite, so that compiler-writers account for it when writing optimizations. Failing that, it's all about profiling so you can measure the actual cost rather than relying on what you think you know about the programming language.
These are good points, but I don't think it relieves you of having to know whether your making a virtual function call or not, or wether you're passing an argument by value or by reference. You do have to know that, and in C++ you cannot know that by looking at the call site. In C and in many other languages you can.
In C there's nothing stopping the function you're calling from turning around and invoking dynamic dispatch.
As a trivial example, objc_msgSend(). This is a C function, but it's also the entry point for objective-C's message sending (e.g. dynamic dispatch).
Basically, what I'm trying to say is, focusing on the cost of the function call itself is useless if you don't know the cost of the function implementation.
Absolutely, but to learn more about the implementation you have to look at the function's code. Every line of that code poses the same problem, so it's recursive down to every last expression.
...which may involve loops or conditionals that then involve tracing more function calls or performing other dynamic dispatch, and so on farther down the tree until you have to know the whole codebase. Eventually you run into the halting problem: you can never know whether a given line in an arbitrary program will be executed, because if you could, just replace that line with "HALT" and you've solved the halting problem.
Can't we just agree that you can't predict the performance of an arbitrary program without measuring it? (You can predict the performance of some programs quite accurately, but that has nothing to do with language choice and everything to do with coding standards that bound execution time.)
If you're saying that it doesn't matter if you know whether you're calling a virtual function or whether a parameter is passed by value or by reference then we cannot agree on that. If you have reason to use C++ at all, these are not things you can simply ignore because they can affect performance rather dramatically.
What we can agree on is that knowing these things is just a small part of what affects performance and that performance can be rather unintuitive.
I agree with a lot of points. However, if the function can be evaluated outside the loop, you should move it, not the compiler. It will - probably - be easier to read and understand; it will be easier to debug when it goes wrong too.
The days when you can reason about performance by looking at function signatures are, in my opinion, long gone.
I can count on one hand the number of times that I thought that changing the function signature of a few methods was going to impact whole application performance. Every time, it was in a low-level function that was called all over the place. Or, more to the point, I dug into function signatures when evidence pointed me there.
I can't even begin to list all of the other problems I worry about that actually do impact whole application performance on a regular basis.
I use C++ daily for systems level programming and I love it. If I could only have one programming language it would be C++. I can write anything in it and I actually enjoy doing so. Maybe I'm weird or something, but I know a lot of other guys who feel this way too.
And, I know some other guys who do hard core embedded C programming and they are migrating to C++. They love it compared to C and can't wait until every line of their C code has been reimplemented in C++.
C++ will be around for decades to come and many folks actually enjoy writing it, they just don't advertise that fact.
Decades might be right. I can't think of a compelling reason for anyone to learn C++ today, but having put in the time to learn it reasonably well, I won't be surprised if I find myself still using it occasionally twenty years from now.
I started learning C and C++ about three months ago. My reasoning is, they're used all over the place. My operating system is written using C and nearly all the applications I use are too. I want to be able to modify the programs I use, and I want to be able to write native applications that run quickly. Those are my motivations. And I haven't been this happy learning a language for years.
"I started learning C and C++ about three months ago. My reasoning is, they're used all over the place. My operating system is written using C..."
Your operating system is written in C. Why learn C++? The return of investment of learning just C is much much higher than learning C++. Been there, done that.
Thankfully one of C++11's requirements was to not introduce any new functionality that had penalties even if you aren't using it. Too bad that wasn't a requirement for the original design of the language.
What do you mean, it's not C++ specific? The runtime example is very explicitly fallout from a requirement of the C++ specification, which affects C++ extern inline functions.
The C++ spec includes the standard library. The COW optimization is a common one (although perhaps not anymore with C++11, see other comments). The C++ spec imposes certain requirements on the std::string interface that are the source of the problem documented in that link. In fact, the exact requirements of the C++ spec that cause the problem are talked about, along with an example of what a simple solution could have been (if it had been considered when C++ was being standardized).
So saying it's not C++ because it's standard library is at best a misunderstanding, at worst it's disingenuous. The C++ spec is the cause of this fundamental problem with any attempt at doing COW optimization of std::string.
I've read the first article and I'm not convinced the problem this guy describes incurs real costs in reality. After all, there is no reason to include inline function definition in dynamic objects unless they are called without being inlined or their address is taken; both of which are extremely unlikely for class-inline methods. The author fails to convince me that if C++ didn't require taking the address of an inline functions to yield a consistent value, real-world C++ code could/would run significantly faster.
The second article is pretty much obsolete with C++11, which makes copy-on-write string implementations impractical.
How does C++11 make copy-on-write string implementations impractical? I know the short string optimization is used now, but I don't see how that's incompatible with COW (for strings that are too large to use the short string optimization). And I don't see how move constructor/assignment is incompatible either. Is there some other C++11 change that I'm overlooking?
I can't give you a definite answer, unfortunately, but I believe the more detailed specification of the semantics of string references/iterators in the new standard makes it very difficult to implement std::string with copy-on-write semantics without losing the advantages of such an implementation.
This should be valid code, but with a copy-on-write implementation, writing to the string on line 3 could invalidate the reference created on line 2.
As far as I know there isn't a proper solution to this problem without changing the return type of `std::string::operator[](size_type) const' in some non-standard way.
Did you read the 2nd link? This is a problem even in C++03. The solution is to invoke the copy on the access of `t[1]`, which is what libstdc++ does. That was the whole point of the second link; that using any operator/method that returns a char reference (operator[], at(), etc) ruins COW.
> The solution is to invoke the copy on the access of `t[1]`, which is what libstdc++ does.
No, libstdc++ specifically DOES NOT unshare the string buffer when calling the CONST version of operator[] (really, go look it up). That's the whole point of CoW: to only copy when the string is written (or at least, when you have to pessimistically assume it will be, because somebody has gotten a non-const reference to its contents).
You could "fix" the issue by unsharing the buffer whenever a const iterator/reference to the string is returned, but then you lose most of the performance benefit of sharing the buffer in the first place, because most strings that are constructed are read at some point.
Because of these difficulties we'll see standard library implementations move away from copy-on-write implementations and then the whole issues doesn't exist. That is to say: the performance of the test case the blog author describes will be consistently “bad” because std::string will behave pretty much like a std::deque/std::vector.
The second link is not a good example of C++ imposing a cost for something you don't use. It's just a bad performance trade-off in std::string. The standard library types aspire to be useful in a wide range of performance-sensitive applications, but if they don't work for you, you are free to use alternatives. Programmers at my company use a variety of C++ containers that are faster than the STL containers for particular uses.
The first link is a reasonable example of paying a cost for something you might not use. However, I disagree with the article when it says there's no good reason for it. People really do compare function pointers for equality. A common example is when different functions implement different "strategies" (to use the GOF term.) A simulation might customize the behavior of simulation objects using pointers to different strategy functions, and you might compare the pointers to find all the simulation objects that follow a particular strategy, or to check whether two objects use the same strategy. Since implementations are free to inline functions that are not declared "inline," there would be no valid way to compare function pointers if an inlined function could have different addresses in different translation units.
Adding performance at the expense of sanity should be voluntary, not the default behavior. (It definitely counts as insanity if comparing &my_handy_function and &my_handy_function for equality returns false in a language where function pointers are the only way to treat a function as data.) Anyone who is picky enough about performance to be concerned about link time is on intimate terms with their implementation(s) anyway, so I think it's perfectly acceptable for such an optimization to be available through non-standard flags. Although, if any language were to standardize insanity, perhaps it would be appropriate for it to be C++.
in 1999 I was a total C++ dude. I had to work that year on a port of metal gear solid for PC. I was surprised to see such well written C code by the konami developers, with nice oo system that had certain tcl element to it. There were only comments in japanese but that did not stopped us from understanding how it worked.
Exactly. I started working in video games programming in '93 when C was considered high level, and most games were written in assembler. C has an elegant simplicity about it that both enables you to implement the high level features in the way you want to, without being able to obfuscate your work with hidden or automatically generated functionality. Whenever I've worked on all C codebases I've always had less pain and more productivity than C++.
I'd say the number of people who do object oriented C right is as high or higher than the people who do C++ right, for a given set of people. It's just simpler, with fewer leaky abstractions.
For me, the hidden cost of C++ was the mental overhead of programming in it. I had to be constantly alert about the many many ramifications of almost every line I wrote.
74 comments
[ 3.0 ms ] story [ 126 ms ] threadI guess this is the trade-off with using a high level language. Expressivity vs transparency.
What's especially nice is that TRUE is defined as -1 in that source, not 1, which could easily confuse someone who isn't intimately familiar with C's boolean operators and conditional expressions (since something like if((a < b) == TRUE) { ... } would actually be incorrect).
Even with that in mind, it would still be nice to have a "safer to use" systems language than C++ generally available. Perhaps Go or D can fill that role (though good luck making that happen for game development on Windows...)
Aha! A question I can answer. This one will be easy:
No and No.
In fact, I can go one stronger than that.
Never and never (although they really hoped).
Go is a general-purpose language, it competes more close with Java and Python, but mostly Python and Ruby.
D is just in an awkward place that has been occupied by Java and C#.
Neither are even remotely viable for serious systems work, although they'd work fine for network services.
You can write non-performant code in any language you'd like, and obscure it in non-trivial ways. That's not the fault of the language.
Most of the argument revolves around objects. There are two ways you can run into "hidden" surprises with objects in C++ that the author is pointing out. One involves failing to use the language features given, the other involves writing some seriously odd code.
So for the author's code to have hidden performance concerns, they would need a constructor taking two parameters of the proper type and an automatic conversion operator that converts to a type assignable as their result. Basically, the stars have to align right and you have to knowingly write some dangerous code.Don't get me wrong, you can hide costs pretty easily in C++ and write some atrocious code. But usually this issue is alleviated by following basic best practices. The hard part is knowing what those best practices are.
I do agree that C++ is a complex and hodgepodge language, but I am not sure there are acceptable alternatives yet. It would be great if JVM had native support for value types, i.e., a way to use manual memory management, bypass bounds checks, or call native code without incurring serialization costs.
Yes, I am aware that C# does this (and I like C# and F# a great deal as languages), but a) I am not deploying Windows in production b) Mono is not yet a viable high performance server side platform (it does look promising for desktop and mobile application or for simpler ASP.NET webapps).
C is a beautiful and simple language (one I have strong affinity for) but manually implementing vtables is a bit painful (see a large pure C project for an example of that), nor is there any support for type-safe generic programming (which is an issue I have with Go as well -- even though I fully understand their reasons for omitting generics).
Then again, the reverse isn't true. There are languages (or more precisely their implementations) that just are slow.
Furthermore, good asymptotic complexity doesn't always correlate with faster code for values of n you actually encounter. Linear search is much faster than binary search, because of cache behavior, up to a surprisingly large n on many architectures.
I have had a few experiences of simply porting something rather directly to C++ and seeing 10X speed and memory improvements. The 'hidden costs' in other languages are loads of unavoidable heap allocations and pointer indirection that really don't happen with C++. And C++ compilers do a ton of very smart optimization these days.
With the generic programming constructs and static polymorphism approaches in C++ you can often get really big performance increases over what's practical in C or Java. std::sort in C++, for example, has often been found a few times faster than stdlib.h qsort. It's because of compile time optimizations that languages like C and Java can't do.
There's on the order of a 1,000-1,000,000x difference or more between a Numpy version of what OkCupid does, and what their C++ does, even if you use the same algorithm.
Similarly, when we recently farmed out a string similarity algorithm from Python to Java, with a more expensive and sophisticated approach in the latter, it ended up being conservatively ~10,000x faster.
I love Python and use it for...just about everything really, but until you've actually optimized software at a systems level you really have no idea what you're talking about.
Proper algorithms and data structures are necessary but not sufficient.
It's a pity higher-level languages don't offer better escape hatches when you need them.
The mental effort you have to make to determine the overhead of an expression by looking at the call site is greater in C++ than it is in C.
That's an undeniable consequence of features like operator overloading, constructors, destructors, virtual functions, etc. It has nothing to do with how well you understand the language. The information just isn't there at the call site for you to see.
The question is a different one. It's whether that fact is compensated for by the power and reduced mental effort these features enable elsewhere. Constructors and destructors make RAII possible. That arguably makes memory management a lot easier than in C.
Isn't this just a specific example of saying "the mental effort you have to make to determine the overhead of an expression by looking at the call site is greater in higher-level languages than in lower-level ones"?
It's true, precisely because you are using higher-level abstractions. I'd be more interested in seeing an example where this wasn't true.
The only good reason to use C++ is because you (1) need a level of abstraction that C doesn't give you, (2) need level of performance that no garbage collected language can give you (even compiled ones), and (3) can't even use C + Lua (or some other scripting language), probably because you need the abstraction and the performance in the same place.
(This is an awfully narrow application domain, compared to what C++ is actually used for)
At that point, the performance characteristics of your programs have become part of the specs. You do not want to hide them under the carpet like you would do with a garbage collected language. Yet C++ does. It wouldn't hurt if it syntactically distinguished initializations, casts, calls by reference and so on.
Or take reference parameters. In C++, if you pass an object to a function you cannot know by looking at the call site if the object is going to be copied or not. The parameter could be defined as a reference, but you have to look at the function definition to find out. In C#, in Java, in Python and in C you know it by looking at the call site. These other languages are all over the place in terms of any high/low level categorization.
If you're writing, let's say, scenegraph traversal, it's really tempting to get it working on the PPU and then port it to run on an SPU. A vtable dereference to main memory will crash your SPU job and it is not always obvious why. Obviously, you can't use static analysis tools to make sure the right instructions are transferred, so you have to do it by hand. A modern PS3 game has on the order of low hundreds f different SPU jobs...not much fun. Even discounting that, SPUs will work best when fed branchless parallelized jobs, I wouldn't be surprised to see an order of magnitude difference between a vtable call on a pointer array and a static call on an object array.
Keeping pointers to objects in a polymorphic array is a games performance anti-pattern because of the dereferencing cost, but it's necessary to call virtual functions...there's a hidden cost right there.
Which loop is faster, by eyeballing, and by how much?
I've seen the former style run literally 1000 times slower than the latter...that's obvious? I submit in a world of out-of-order processors it is not at all.I don't think that all the author's points are spot on...Koenig lookup is Byzantine but IDEs do a good job of it, ditto source-level reasoning about dispatch. The underlying theme, that C++ is not a good fit for modern game development, shouldn't be so trivially dismissed.
It depends where *this is allocated. In the worst case, it is allocated in main memory while you wanted to stay in the graphic memory, or something.
A naive compiler would then access memory (or the cache) instead of using registers. A Sufficiently Advanced Compiler would guess that calling ++ many times is the same as incrementing in one go, and hoist that out of the loop, but apparently this one is a bit cruder.
Now the same could be said about m_numAssets, but this one isn't written to, so the compiler only have to put a copy in a register, which I guess is a simpler optimization to do.
To answer your question, that particular situation would be better in any language that forces you to explicit the reference to "this" (or "self"). Imagine how we could modify C++:
Applied to the example in the GGP above: We see that every non-local access is prefixed by something ("this->" and "a[" here). The heavier syntax suggests a heavier cost, so the programmer will more easily think of hoisting those out of the loop, if possible (either manually or through compiler optimizations).I explained that here: http://news.ycombinator.com/item?id=4540107 I did not get the Load Hit Store problem, but I did sense there was a problem with the repeated read access to memory.
By the way, it looks like your problem is a bit more subtle than the textbook Load Hit Store: you don't read the location you just wrote to. I guess the write access dirties more than one word (at least a cache line, maybe more).
If I remember correctly somewhere further down in that particular Linus rant he was saying that you can look at a call site and tell what's going on, the same argument the poster here is making, but do we honestly believe Linus never uses function pointers? And the argument about the name of the function giving us a rough estimate of the overhead does not sound valid to me.
As a C programmer and former C++ programmer I can see a valid argument hidden in these types of posts but I think we'd be better off with a little less attitude and a little more useful guidelines.
ObjectiveC has a much smaller call site complexity penalty.
I believe a beginning programmer will be best off using fixed arrays at first, then reimplementing a basic version of std::vector when the need for it arises (which it inevitably will) and then use whatever vector datatype his chosen language offers him. This will give the programmer an understanding of what is actually going on and why the array is suddenly resizeable/resizes itself. That's the sort of attention to detail that separates a good programmer from a great one.
> do we honestly believe Linus never uses function pointers
Actually, they're all over the place in the kernel. They're used all the time when writing device drivers. I use them myself in every bit of quasi-polymorphic C code I write (although I'm not a kernel hacker). I think they're quite nifty actually although they can obscure things. Here we come back to how we're using constructs a language gives us. Used correctly, specifying well documented interfaces, they give birth to hardly any problems. Executed the wrong way, they give birth to all sorts of bugs and weird behaviour and obscure the code.
> the argument about the name of the function giving us a rough estimate of the overhead does not sound valid to me. It only will if you know the function, yes. Otherwise it's a weird argument.
I think your opinion is just as valid as Linus' is. I think, larger languages open the flood gates for decisions which can come at a cost for performance or maintainability. Programmers should be aware of that and useful guidelines would definitely help.
One of the most important aspects of balancing productivity with performance is knowing which parts of code are crucial to performance and should be written with that in mind. That's one of the toughest parts as well. Usually one is better off profiling than guessing but I believe an educated guess can go a long way nevertheless.
Will that function call be inlined? Does it have side-effects? Are the arguments constant, so that it might even be evaluated at compile-time? Can it be hoisted out of a loop? Do its pointer args alias, preventing many of these operations? Is it big enough to blow the cache, or will it all fit in L1 and benefit from really fast memory accesses? Can branch-prediction operate effectively on its loops, or does it have an unpredictable pattern that will flush the pipeline many times?
I've heard that the best way to get your program to run fast is to get it into a compiler benchmark suite, so that compiler-writers account for it when writing optimizations. Failing that, it's all about profiling so you can measure the actual cost rather than relying on what you think you know about the programming language.
As a trivial example, objc_msgSend(). This is a C function, but it's also the entry point for objective-C's message sending (e.g. dynamic dispatch).
Basically, what I'm trying to say is, focusing on the cost of the function call itself is useless if you don't know the cost of the function implementation.
Can't we just agree that you can't predict the performance of an arbitrary program without measuring it? (You can predict the performance of some programs quite accurately, but that has nothing to do with language choice and everything to do with coding standards that bound execution time.)
What we can agree on is that knowing these things is just a small part of what affects performance and that performance can be rather unintuitive.
I can count on one hand the number of times that I thought that changing the function signature of a few methods was going to impact whole application performance. Every time, it was in a low-level function that was called all over the place. Or, more to the point, I dug into function signatures when evidence pointed me there.
I can't even begin to list all of the other problems I worry about that actually do impact whole application performance on a regular basis.
She also has a followup where she explores the "pressure points" of writing games in C++:
http://www.rachelslabnotes.com/2009/11/there-are-never-enoug...
And, I know some other guys who do hard core embedded C programming and they are migrating to C++. They love it compared to C and can't wait until every line of their C code has been reimplemented in C++.
C++ will be around for decades to come and many folks actually enjoy writing it, they just don't advertise that fact.
Except that I also enjoy some Lisp from time to time.
Your operating system is written in C. Why learn C++? The return of investment of learning just C is much much higher than learning C++. Been there, done that.
I've been concentrating too much on C++ I think. This thread has prompted me to go back and spend some time writing some C.
1. http://ridiculousfish.com/blog/posts/i-didnt-order-that-so-w...
2. http://ridiculousfish.com/blog/posts/i-didnt-order-that-so-w...
Thankfully one of C++11's requirements was to not introduce any new functionality that had penalties even if you aren't using it. Too bad that wasn't a requirement for the original design of the language.
So saying it's not C++ because it's standard library is at best a misunderstanding, at worst it's disingenuous. The C++ spec is the cause of this fundamental problem with any attempt at doing COW optimization of std::string.
The second article is pretty much obsolete with C++11, which makes copy-on-write string implementations impractical.
For example, consider the following function:
This should be valid code, but with a copy-on-write implementation, writing to the string on line 3 could invalidate the reference created on line 2.As far as I know there isn't a proper solution to this problem without changing the return type of `std::string::operator[](size_type) const' in some non-standard way.
No, libstdc++ specifically DOES NOT unshare the string buffer when calling the CONST version of operator[] (really, go look it up). That's the whole point of CoW: to only copy when the string is written (or at least, when you have to pessimistically assume it will be, because somebody has gotten a non-const reference to its contents).
You could "fix" the issue by unsharing the buffer whenever a const iterator/reference to the string is returned, but then you lose most of the performance benefit of sharing the buffer in the first place, because most strings that are constructed are read at some point.
Because of these difficulties we'll see standard library implementations move away from copy-on-write implementations and then the whole issues doesn't exist. That is to say: the performance of the test case the blog author describes will be consistently “bad” because std::string will behave pretty much like a std::deque/std::vector.
The first link is a reasonable example of paying a cost for something you might not use. However, I disagree with the article when it says there's no good reason for it. People really do compare function pointers for equality. A common example is when different functions implement different "strategies" (to use the GOF term.) A simulation might customize the behavior of simulation objects using pointers to different strategy functions, and you might compare the pointers to find all the simulation objects that follow a particular strategy, or to check whether two objects use the same strategy. Since implementations are free to inline functions that are not declared "inline," there would be no valid way to compare function pointers if an inlined function could have different addresses in different translation units.
Adding performance at the expense of sanity should be voluntary, not the default behavior. (It definitely counts as insanity if comparing &my_handy_function and &my_handy_function for equality returns false in a language where function pointers are the only way to treat a function as data.) Anyone who is picky enough about performance to be concerned about link time is on intimate terms with their implementation(s) anyway, so I think it's perfectly acceptable for such an optimization to be available through non-standard flags. Although, if any language were to standardize insanity, perhaps it would be appropriate for it to be C++.