[Supposedly as compared with CeePlusPlusSlowerThanCee]
Mainly from those who "love" CeePlusPlus there is an argument made the C++ programs aren't really slower (or larger) than functionally equivalent CeeLanguage programs.
- From your experience, which elements of the C++-language are FORCING a C++-compiler to "throw in" additional instructions without any benefit?
- I assume you don't truly mean "without any benefit", and that this phrasing can be improved. Completely useless instructions are usually tossed by the optimizer. Some instructions generated may be suboptimal, on the other hand, while still having some kind of benefit.
- Or do we just [?] unfair comparison? (On the first system on which I used C++, executable programs were statically linked in C++ but dynamically linked in C - so giving the impression that a simple "hello world" program in C++ required a MUCH larger executable than the same program in C.)
- Are there other factors why C++ programs appear to be larger than equivalent C programs? (E.g. traditionally C++ was aiming to much larger systems than C, libraries for C++ were of much coarser granularity than such for C; this could effectively lead to pulling in all kinds of floating number support and even math library if you tried a simple char * output, because overloaded output operators pulled in the same object file from the library which also contained output of floating point numbers, which in turn pulled in more and more without really being used.)
Your questions above don't seem to quite fit the intent of this page title.
- You're right of course, so feel free do delete or rephrase them. One major intent when creating this page together with the other one (named at top) was to draw discussion away from another place where it came up and give it at a more appropriately named page title.
Well, one of the obvious issues is the vtable. Virtual functions force presence of a vtable, which expands every such object, sometimes dramatically, and also somewhat slows down otherwise lightweight method calls, since an indirection must be done.
- You only pay for the vtable for classes that need it; PlainOldData classes/structs don't have VeeTables?. Any structure which is valid C code is a PlainOldData structure.
- True, but using plain old structs is not good OO style, and if one is using an OO language, why not use good OO style? PrematureOptimization?
- Agreed wholeheartedly. The point that I was trying to make was that C++ (for better or worse) has adopted the philosophy that "you don't pay for what you don't use". The initial discussion concerns "functionally equivalent C/C++ programs"; the trivial case of which is a C program that is compiled with a C++ compiler. Most well-written C programs (excluding those using new AnsiCee features) will compile with a C++ compiler, either without modification or with trivial "syntax" modifications (such as adding parentheses around ? : operators, inserting appropriate casts, or removing variables named "class", "template", etc.). The output of such a process should be as fast, or nearly as fast, as the same program compiled with the C compiler.
- That said, there are some features in C++ that a converted C program would have to "pay for" (with either slightly reduced performance or a slightly bigger memory footprint). ExceptionHandling is one of them; unless one disables exceptions with a compiler switch (in which case, one is using a somewhat-incompatible dialect of C++ rather than standard C++), each ActivationRecord that might have an exception propagate through it gets additional code and data to handle UnwindingTheStack.
- Use of the C++ compiler rather than the C compiler is likely to pull in additional libraries, even if the code avoids any C++ library functions. Perhaps a sufficiently parsimonious C++ compiler could detect the code it is compiling is free of C++-specific stuff and avoid doing this; but I suspect the mere act of compiling with a C++ compiler means you'll get at least part of the C++ runtime support linked in, on most platforms.
It depends on the program, but there are cases where that all by itself can add up to a dramatic increase in time and space both.
A counterargument might be that methods should always be so heavyweight that the overhead of the vtable indirection is negligible. There are many such programs, but it is questionable that this is the best OO style.
Another counterargument potentially is that virtual functions should be rare enough that this time/space overhead shouldn't dominate.
The pointer-to-vtable typically expands each object by exactly one pointer. This isn't much. Also note, that you would need the equivalent of the pointer in C as well if you want to manually implement DynamicDispatch using PointerCastPolymorphism, if not, you wouldn't need virtual methods in the first place.
- If you want to, yes, however that's not the usual equivalent in C to virtual methods; the usual equivalent is patchwork logic.
- Agreed again; though simple examples of DynamicDispatch can be done with function pointers; no obnoxious casts needed. (Simulating polymorphism, on the other hand, is another animal).
[
I would disagree with the vtable argument. I always felt that giving up jump tables when I went from assembly to C was a step backwards. C++ restored them with vtables. Jump tables/vtables were always more efficient in terms of code size and execution time over repeated if/else if or switch statements.]
Both of those comments are, unfortunately, incorrect, and misunderstand C++:
- [I retract this; I was suffering from brain damage, and there is no value in my comment at all; I was simply mixing up issues from a different topic altogether; but there is, potentially, value in the responses to this:] There is no "pointer-to-vtable" as such; there is instead an (unavoidable) pointer to the object, and objects with virtual functions contain vtables, which in one sense are no different than any other instance data. But that's a minor issue and not what I'm talking about anyway. The cost that I am referring to is the size of the vtable, which contains one pointer (to a method) per virtual method in the class. If you have 100 virtual methods, you have 100 vtable entries. If the object has only one item of instance data, say an integer, then you now have 100x overhead on space compared with the typical way it would be done in C: a struct containing just the integer.
- This cost is paid for once per class, not once per object. If you have a 4-byte class with 100 virtual functions, then you have a 400-byte overhead (assuming 4-byte pointers) for the class, and a 4-byte overhead per object. If you have one instance of that class, your overhead is 404 bytes or so. If you have 10 instances of that class, your overhead is 400 + 40 bytes; not 4000 bytes.
- On the other hand, it is possible in C to have a struct full of function pointers, to parallel this approach, and it would have the same overhead. However, this is rare in C, whereas using virtual functions in C++ is very common, and actually forced in many cases.
- As for assembly jump tables, yes they are handy, but as I just said, in many situations you can do something similar in C with function pointers - but more importantly, it is not true that jump tables and vtables are always more efficient than switch statements. Absolutely not. C compilers implement switches using generated jump tables!!! (There are also two other ways they can be implemented, and the compiler estimates which of the 3 will be most efficient and chooses that - and this is the classic technique, not a recent development)
Huh? That sounds like an incredibly stupid C++ compiler. The speed cost of a pointer to the vtable is an extra indirection: it's (*(object->_vtable[function_index]))(args) instead of (*(object[function_index + vtable_index]))(args). The space cost is (data members + function members) / (data members + 1). Both of these costs are known at compile time: the compiler has to know how many data members there are in order to lay out the object, and how many function members in order to build the vtable. So why can't it, based on whatever speed/space tradeoffs it uses for inlining, decide whether a pointer to a shared vtable or an inline vtable is more appropriate? --
JonathanTang
In fact, VeeTables were one of the things I had in mind when I created this page. Usually I tell my students that the alternative to a VeeTable is a an additional flag as discriminator within the object (or struct, if we move from C to C++).
- While TagBits are a useful technique, especially within the context of dynamic languages like Lisp implementations or SmalltalkLanguage, for distinguishing between types of "small" objects and pointers; they aren't an adequate substitute for a VeeTable in the general case. There are other tricks which can be used:
- BoxingConversions and BoxingAnalysis? can eliminate the need for boxed/tagged data structures in many cases. (This is one of many uses for TypeInference in an otherwise dynamically-typed language. It also has useful implications for the garbage collector, if present - see TaglessGarbageCollection).
- For stuff on TheHeap; allocate ObjectsWithVeeTablePointers? and SmallObjectsWithout? in separate memory regions (arenas)
This would more or less outweigh the overhead on the data side (per object VeeTable-Pointer). Furthermore, it saves a number of decisions implemented on the client side (which is typically a bad thing considering maintenance costs). Opinions seem to differ here, so does anybody have HARD NUMBERS on this, e.g. from refactoring an application from one style to the other and comparing costs = size, speed, later maintenance effort? Well, hard numbers on the latter will probably not impossible, as you had to continue the original AND the refactored program and applied the same changes to both, But maybe someone has done this with his/her students, e.g. splitting a class (students I mean here :-)) and let only one half work OO style.
- Most conversions from C to C++ don't involve creation of lots of classes with a) small data footprint (such that the VeeTable pointer is significant overhead) and b) many instances of these classes. An "int" in C++ has no VeeTable pointer; and since non-objects are first class in C++ there is little need for things like Java's Integer/Double/Boolean/etc. classes (which have lots of additional overhead besides the Java equivalent of the VeeTable), unless one wants to treat these polymorphically. In many cases in C++, templates are a better solution.
"Jump tables/vtables were always more efficient in terms of code size and execution time over repeated if/else if or switch statements."
Have a look at the implementation of SmallEiffel, I think it specifically contradicts this statement. The whole-program optimizer for SmallEiffel
translates dynamic dispatches to a set of known candidate methods into an if-then-else structure that chooses the correct method and calls it statically. They found that on modern hardware (deep pipelines, heavy reliance on branch prediction, etc) this was actually faster than using vtables.
One area where C++ programs tend to be slower than C is that C++ programs tend to have more methods of smaller size. This leads to more subroutine calls and returns, which take more execution time (I don't think it is a significant difference, but it is there). Although this is more of a code structure issue than a language issue, C++ seems to reinforce the object approach while C reinforces the procedural approach.
The flip side of this is that those tiny C++ methods are often (usually) inlined. The C++ code is just as fast; it's just written in a style which is easier to read and modify. But here's the catch: C derives its performance from being simple, while C++ relies on good compiler optimizations to generate fast code. The result is that a good optimizing C++ compiler will produce code that is just as fast as C in most situations (even though the programmer is using the more powerful features of C++). However, bad code can easily be slower than C and--here's the rub--*unless your C++ compiler is really good* you might not get a fast binary.
Anything that can be written in C can be written at least as well in C++. If the problem calls for tight, quick code, you use C++ the same way you would C. If the problem calls for powerful abstractions and reusability, you use more advanced features of C++. Most applications call for a mixture of the two: small, fast code at the low level and inner loops; broad abstractions for higher-level code.
I say C++ is at least as good as C, because C++ is more expressive. Therefore, it can do what C can do, plus a lot more. Even as just "a better C," C++ is indeed a better C. Features like inline functions and templates outdo #defines, because they allow the compiler to use its specialized knowledge in order to generate better assembly code. Even simple classes I have used to generate easily readable, easily maintainable, low-level code that's at least as fast as the equivalent C code. (I know it is because I looked at the generated assembly!) But in all cases, the C++ code was easier to read and easier to maintain.
For higher functions, C++ provides higher-level features. The key is to choose wisely based on the cost and benefit of each feature.
(This should all be obvious. The C++ code would not be slower or more obfuscated if I were trying to write clear, fast code. Since C++ is more expressive, C++ gave me more tools I could use to achieve that goal, and I used the language in pursuit of that goal. Therefore, C++ gave me at least what I could get out of C, plus even more. If a C++ feature would lead me further from my goal, I chose not to use that feature. The worst I could possibly do would be to end up with C++ as a better C. Therefore, C++ is at least as good as C in meeting my goals.)
Since C++ is more expressive than C, there is no good reason to choose C over C++. Objections to C++ in my experience have come (a) from programmers who spent years learning C but haven't taken the same time to learn the distinctives of C++ or (b) from those who repeat unverified rumors. Unfortunately, much C++ code has been generated by the former, lending credence to the latter.
Actually, I can think of two reasons you might choose C over C++ (though they only apply in some rare situations): (1) you need the widest possible portability, or (2) you need to target platform(s) for which no decent C++ compiler exists. This might happen to you in the embedded space, for example. Widespread porting of GnuCee and GnuCpp has made this less of an issue than it used to be.
I can think of a few good reasons to choose C over C++:
- You've got a one-off project and don't have time to learn C++. Of course, you could just learn the C-subset of C++, but then you're really learning C anyway. [I think this would only apply to someone who already knows C. If one knows neither C nor C++, the learning curve for either one does not appear to be different.]
- If you already know C, you pretty much already know the C-subset of C++, as there are very few differences, all somewhat esoteric. From this point, you can learn incrementally to improve your code with various C++ features. What you're really saying is that you don't have time to learn C++ all at once, because you can't justify it without a basis of experience, so you need to take a migratory path.
- You can always use the C++ compiler, but limit yourself to a subset of the language. (Of course, if you choose not to use templates, for instance, much of the library won't be available to you).
- You're writing a library that has to interface with other languages. C FFIs are generally more well-developed than C++, because C FFI's are trivial to implement, while most OO languages have incompatible object models (unless targeting a platform which imposes an object model; such as COM or DotNet) and calling one from another is often tricky.
- Any C FFI can be called from or implemented in C++. But if you use C++, you have more expressiveness available to you in your implementation. Therefore, use the C FFI, if it's better, but implement in C++.
- ExternCee? is your friend. :)
- The target platform doesn't have a C++ compiler.
- This might've been an issue 5 or 10 years ago. It isn't today.
- In the data center, this is true. In EmbeddedSystems, it's not; there are numerous processors out there that still don't have useful C++ compilers (if any at all). In many cases, the processor in question is very application-specific (a DigitalSignalProcessor or a communications processor, as opposed to a more "general purpose" CPU) or very low-end--I can't imagine wanting to use C++ (in all its, ahem, glory) on an 8051 (which has a hard enough time hosting a legitimate C environment). But C++ compilers are often written long after C compilers for such platforms. And Cfront, or something like it, isn't always a reasonable solution (is there a high-quality, standards-compliant C++ to C translator available today?).
- No, and there never was, there was only cfront. :-)
- You've coded in C for an 8051???
- Actually, I somewhat disagree about availability. It may not be advertised that C++ is available for some processor for embedded environments, however it has long since been almost universally the case that the chip vendor has gotten the GNU toolchain to work for the processor, first and foremost, because it's cheaper. (This is not hypothetical speculation from a GNU fanatic, I'm speaking from experience, having assisted in doing the compiler retargeting for some of those chips.) C may have been the primary language they focussed on, but to some extent you get front-ends for other languages (including C++) that share the common code generator. Although those other languages may be more buggy. :-)
- That's partly nitpicking, but mostly what I mean is that, if one doesn't explicitly ask about the GNU toolchain, one might not find out that it's available for a particular chip.
Granted, none of these have anything to do with performance, and hence are a bit off-topic for this page. And I do think most objections to C++ (as opposed to C, not as opposed to
CommonLisp ;) come from people who don't know what they're talking about. But there're several real-world projects (eg.
ParrotCode and GTK) that are much easier to work with because they're in C instead of C++.
See also http://www.objectmentor.com/resources/articles/WhyAreYouStillUsingC.pdf
EditHint: Does this last section belong on a different page? Which one?
CategoryCee; CategoryCpp