views:

253

answers:

6

As a programmer I use to follow TDD, beginning by writing a simple version of my code then heavily refactoring it. When refactoring I usually just listen to my code. I search for bad smells and then refactor the code to remove smells. Typically I will introduce new methods to avoid repeating code, move class members around to put them nearer to the point where they are used, try to avoid passing around too much parameters (solution may be to introduce new objects, or store some parameters in current object at instantiation time) and so on.

The rules I follow are designed to make code simpler, easier to read and to maintain. That is the main reason I'm doing all this.

But as a side effect, it also produce faster code, and from my first-hand experience that is especially true when I'm writing C++ code. It is less obvious when writing Python code, (my second most often used OO programming language), as the cost of introduction of new functions can be high in Python, but for C++ all I have to do is to make them inline. In the last few month I ported a large program from C to C++ and on the way it became both much simpler and much faster.

On the other hand I've often heard (and read) people arguing that OO programming is slower by design that non-OO programming. And I've heard that especially about C++.

What I wonder is what makes them say so, as it is so different from my personal experience.

I wonder about the methods and habits they have when writing OO code. Do they also use refactoring as I do, or may mostly follow design patterns, or heavily use STL ? And from where comes the slowness they perceive. What could they have done differently that would give them more speed have they not done it the Object Oriented way ?

Any feedback from OO programmers would be welcome either if they get more speed as I perceive I do, or the opposite they get slower code. Concrete examples would be most appreciated.

EDIT: looking at first answers, I believe I have not been clear enough in my question. The question is not about is OO faster or slower than non-OO programming on theoretical ground. But from practical experience what kind of OO practises could lead to slower C++ code that equivalent programs writtent using C. My claim and personal experience is that Refactoring that I see as an OO practise (not as a definition of what OO is) makes programs faster. Comments on answers suggests that heavy use of iostreams would make C++ programs slower, but that should not generally be true for most of STL content. I have a strong feeling that over-engeeneering, abuse of UML design before coding or abuse of design patterns could lead to performance issues, but as it is not the pathes I'm following daily I have no first hand experience and may be completely wrong.

+8  A: 

This used to be an evergreen topic especially between C and C++ developers. It is based on some facts:

  • OO programs tend to use inheritance and polymorphism, thus virtual methods; and virtual method calls are slower than nonvirtual (static) calls due to an extra pointer indirection
  • OO programs tend to be composed of many smaller objects, and often (even inadvertently, due to implicit constructor calls) create and destroy a large number of (temporary) objects; memory allocation and object creation is expensive in C and C++

So, just as it used to be true that really well hand optimized assembly code is faster than its equivalent C version, a really well written piece of C code is most likely faster than the equivalent C++ code. However, just as it is with assembly, it is also most likely longer, took more time to write and is more difficult to understand (and thus maintain).

Many years ago, when speed and memory efficiency was a major concern, most such optimizations were worth their price - hand optimizing C (or even assembly) code could make the difference between a usable app vs a uselessly slow/bulky app. Nowadays, due to vastly improved processor power and smarter optimizing compilers, the speed difference would be barely if at all noticeable in most cases (i.e. using a typical application on a modern machine). Whereas the savings in development and maintenance costs are noticeable.

And the flip side of the coin is that since a good OO program is smaller, cleaner and easier to understand, it is also easier to see the most common path and optimize for that (typically on a much higher level than it is possible in C though, since an OO language allows you to reason on a higher conceptual level about the problem domain). Not to mention the greatly improved standard libraries whose performance is hard to match in most cases (even by hand optimized assembly - since they may well use that behind the curtains if they really need to).

Péter Török
"virtual method calls are slower than nonvirtual (static) calls" Chances are you'll never notice this. If after refactoring your program is much slower than it once was, you're problems surely go way beyond virtual method calls.
John Dibling
Besides which, in cases where you need polymorphism you must have been doing a switch in the non-OOP version, which introduces the same exact overhead as a virtual call, if not more.
John Dibling
@John: I disagree. The reason cstdio often outperforms iostreams despite the need for cstdio to parse format strings and such is the result of iostreams' heavy reliance on virtual functions. More to the point, virtual functions are often, if not always, used as the example of performance reductions as a result of using an OO model.
Billy ONeal
@Billy: Used by detractors of OO models. Not exactly an unbiased evaluation, and almost always contrived in such a way as to provide an apples-to-oranges comparison where the OO model does much more work.
John Dibling
@John: It's used by OO detractors, yes, but that does not mean it's an invalid argument. As I said, C++ `iostreams` are the canonical example.
Billy ONeal
@John, I didn't claim you would notice the difference (in general), but it is a measureable fact nevertheless. While editing and extending my answer, I have added more explanation regarding this, which hopefully makes my point clearer.
Péter Török
@Billy ONeal: I agree with you on iostream, but I rather see that as a typical exemple of bad design choice in STL, or at least over-engeenering, not as an inherent OO design weakness. iostreams are not KISS at all and I do not imagine how you could get to that following the TDD and refactoring path. I usually avoid them as extending them and maintenance of code relying on them is one of my personal nightmares.
kriss
@Billy: Two additional things. One, I don't think its accurate to say that streams rely "heavily" upon polymorphism. Two, its an invalid argument because its apples-to-oranges. Yes streams are slower. I dont like them myself, and I almost never use them in production code. But I recognize that they do a ton more work than sprintf does, such as allocating dynamic memory, and that's why they're slower. People who scream "OO is slow, just look at streams!" conveniently forget or ignore this.
John Dibling
@John: Well duh. The definition of a fast program is one that does less work. The only thing that makes a difference w.r.t. programming models' speed is what a canonical design looks like. Streams are the canonical OO i/o design, cstdio is the canonical structured i/o design.
Billy ONeal
@John: For the record, I generally agree with you and generally am an OO proponent -- but there are valid issues with the model (as there are with any programming model). Ignoring them and having a knee jerk reaction against anyone who mentions them is just as bad as someone who takes that one problem and parrots it constantly. The OP asked why some people say OO is slow, and virtual functions are a reasonable answer to that.
Billy ONeal
@John if you don't see the virtual call overhead in your programs, you don't have interesting performance constraints. I can assure you that the virtual call overhead does show up when you have only 16ms to run your entire application. A switch statement can greatly outperform a virtual call simply by not having to do the extra pointer follow to get the function address. (A cache miss means hundreds of cycles down the drain.)
dash-tom-bang
Speed difference or not, I'll take iostreams over cstdio any day. I've mismatched format symbols, parameter types, etc. too many times. Iostreams are safer and easier to understand. That may be slower when running the code, but saves hours and hours of software maintenance.
John Gaughan
@John: Same here -- I don't generally use cstdio. I don't generally have "interesting performance characteristics" or care about these sub-millisecond kinds of overheads. But I acknowledge them rather than irrationally act like they don't exist.
Billy ONeal
@Billy: I agree. I make choices purposefully and deliberately. I choose the method that is safer, even if slower. I like knowing that merely selecting a different library closes an entire class of software defects from being a factor.
John Gaughan
+3  A: 

If the question boils down to "is OOP slower than non-OOP?" then the answer is no. At least not on its own.

When you refactor large chunks of code, you are really redesigning things from a base level up. If you redesign some component, but not the pieces that fit together with that component, then at some point your'e going to have to build a bridge between the old and the new. This often manifests itself in additional code you have to write to translate from the Old Way to the New Way, and this introduces slowness.

But it's got nothing to do with OOP itself. You could say the same thing about any other two programming paradigms.

You can also introduce slowness if you do OOP badly. But this again has little to do with OOP itself, and everything to do with the programmer who wields it. Or tries to wield it.

John Dibling
+3  A: 

You are confusing OO with separation of concerns.

OO is based on three pillars:

  1. Encapsulation
  2. Polymorphism
  3. Inheritance

Specifically, a function call with polymorphic behavior usually results in a clearing of the processor's pipeline, because indirect function calls are relatively difficult to branch predict. There is also additional space needed inside the object for book keeping of the virtual function to actually call, as well as to allow for casting and such.

Separation of concerns though, can result in faster code, because it makes it easier to do the efficient thing in all cases. It also makes it simple to find the portions of your program which are taking the greatest amount of time to execute. Finally, it allows you to improve the general performance of the system because it reduces code size, which can allow more of your application to fit in the processor's cache.


EDIT (In response to question edit):

The question is not about is OO faster or slower than non-OO programming on theoretical ground. But from practical experience what kind of OO practises could lead to slower C++ code that equivalent programs writtent using C.

Object orientation has nothing to do with the language in which the code is written. C++ has facilities that make it easier, but you can write object oriented code in C as well. Object orientation is a way of thinking about a programming problem or domain, not necessarily the word class being a keyword of your programming language.

My canonical example is cstdio, versus iostreams. Iostreams is a canonical object oriented I/O library, cstdio is a canonical structured I/O library.

You could implement something like streams in C, but then it would still be object oriented -- there'd just be no language facilities there to make it easier for you.

Billy ONeal
I do not see the point of the first paragraph. Are you saying that switches are more easy to branch predict that jump tables ? Or even worse than programmers should prefer cascading ifs (executing more code) to go faster ? And why would you introduce a new inherited class anyway if there is no need to ? Also vptr table used to implement virtual functions exists only once for each **class** not on every instance, and dynamic typing is quite rare in actual C++ programs. And C++ function level polymorphism is static and performed at compile time.
kriss
@kriss: No. However, OO programs get themselves into cases where they'd need a switch-like structure much more often than non-OO programs. Oh, and there is at least the size of a pointer overhead for the vtbl per object, in addition to the per-class structures. There is no such thing as dynamic typing in C++ programs because C++ is not a dynamically typed language. With the exception of templates, there is no such thing as polymorphism that is not handled at runtime -- that's what polymorphism means. A function call where the actual function to call is known at compile time is not polymorphic.
Billy ONeal
@Bill: I agree, 1 pointer by instance using virtual functions. Not much. You can easily loose more using C through struct field padding. I disagree about dynamic typing. You should have a look at RTTI http://www.devx.com/getHelpOn/Article/10202/1954. You also should have a look at the definition of polymorphism, its's about having identical interfaces, not runtime/compile time.
kriss
@Billy: "OO programs get themselves into cases where they'd need a switch-like structure much more often than non-OO programs" Please back up this claim. If you're writing OO and need a bunch of switches, you're doing it wrong.
John Dibling
@John: I mean that in an OO program, you encode information about the state of the program in the type. You therefore put yourself in situations where you need to make decisions based on the type of item you're looking at. That requires something like a big switch or a polymorphic interface. @kriss: RTTI is not dynamic typing. Dynamic typing is being able to do "10" + "56" = 66. In C and C++, you need to parse the string into an integer first.
Billy ONeal
@Kriss: W.r.t. polymorphism, how are you going to have multiple available types matching the same interface at runtime and resolve the target of the function call statically? That doesn't make any sense. Yes, the dictionary definition of polymorphism talks only about interfaces, but if you're not making a virtual function call through a base class pointer (which cannot be resolved at compile time), then you are not using polymorphism (Unless you are talking about templates).
Billy ONeal
@kriss switches are often implemented as jump tables, and virtual functions are neither. You can't predict switches very well, but at least (for small cases) the code may be in the cache. For a virtual call, the code will likely not be in the cache.
dash-tom-bang
@Billy ONeal: about polymorphism obviously if you exclude all static use case (remove templates, remove polymorphism on functions and C++ does not support abstract algebric definition like say Haskell) all that will be left will be runtime calls. That does not put runtime in the definition of polymorphism, that's all. About RTTI, I do not get your point. Languages that do the kind of thing you describe (like Perl) do parse strings, no magic. RTTI provide ability to work with a weakly typed hierarchy of objects (even if you still have to cast them) and that is the point of dynamic typing.
kriss
@dash-tom-bang: nothing forbid compiler writer to implement inheritance through some type value (say RTTI) and a few switches. I believe them as imaginative enough to do it if cache usage is better that way. OK, for reasons of separate compilation of classes they would probably be obliged to also keep vptrs table. But that's more a linkage problem that a real limitation of OO programming (and vptrs are not that bad anyway).
kriss
@kriss: How can a function be polymorphic? Without an object to give it a type, it's not polymorphic. And there are languages which do exactly as I describe, such as JavaScript and PHP. C++ does not have [dynamic typing](http://en.wikipedia.org/wiki/Type_system#Dynamic_typing). You are getting confused by the fact that dynamic has two meanings: 1. known only at runtime, and 2. "dynamic typing". The first is what C++ has, the second is what is meant when someone is talking about a dynamic type system.
Billy ONeal
@kriss: While nothing forbids compilers from implementing polymorphism that way, it doesn't matter what they can do, what matters is what they do do.
Billy ONeal
@Billy ONeal: I wrote a compiler (a C compiler) some times ago and what I'm sure of is that compilation techniques changes quite often and are different for each implementation. As vptrs are not a requirement, just a possible implementation I'm quite sure if any optimization is actually possible it will be done soon, if it has not already been by some implementation.
kriss
@Billy ONeal: about polymorphism you seems to be confusing interface and signature. Interface is merely function name and number of parameters, polymorphism in this case is just having different definition for different types of parameters. Polymorphism is not just about the type of the first implicit parameter of a function (ie: not just about object and methods). Your line about dynamic typing is strange: Dynamic typing is exactly that, ability to write code using variables whose actual type will be known only at runtime. 1 and 2 are the same thing, that is what Python or Lisp have.
kriss
polymorphism in this case is just having different definition for " different types of parameters." <-- Okay, if you want to be pedantic about it. But if you ask any C++/Java/C#/anything programmer about polymorphism, they are referring to http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming , which has nothing to do with overloading. When you mean overloading, say overloading, not polymorphism. Because the chance of confusion is high.
Billy ONeal
@kriss: If you can find me a well supported C++ compiler that implements polymorphism in terms of jump tables, I'll give you a million dollars. I know Comeau, Intel, Microsoft, Clang (LLVM), IBM, Borland and GCC don't do it.
Billy ONeal
@kriss you cannot implement C++ virtual functions with jump tables. Linking is the obvious one, but remember that dynamic linking doesn't even require the objects to be buildable at the same time. Not sure how you'd arbitrarily extend a compiled-in jump table.
dash-tom-bang
@Billy ONeal: If I add it to gcc will you give me a million dollars ? What I have in mind may imply to generate several versions of the same function one for local optimizations and one for external linkage, it may also have some restrictions on wich functions or classes it may apply to, but it looks not worse that the kind of restrictions we have to accept for inlining. Seriously I will probably add an exemple containing virtual functions and some timer as first example of OO and non OO style. Until now it looks like the more (the only?) serious speed objection to OO programming.
kriss
@kriss: Did you read @dash's comment? Had I known you were asking this question just to argue I'd not have answered it.
Billy ONeal
A: 

Ignorant people say ignorant things. No real reason trying to figure out what reason is behind what they say because there generally isn't any.

Noah Roberts
Unless you need to argue with them to stop the ignorance - or decisions based on it - adversely affecting you :-/.
Tony
Stating that opinions contrary to one's own are ignorant does not make it the truth.
dash-tom-bang
@Tony - meh...generally also a waste of time and effort. People claiming crap like, "OOP is slow," don't generally even care if they're actually right or wrong. Don't bother arguing with THEM...just expose their ignorance to others by writing more maintainable code, that runs just as fast, and do so quicker than they ever can.
Noah Roberts
@dash-tom - "OOP is slow," is not a real opinion. It's just methodology bigotry and has as much intellectual worth as any other kind of bigotry.
Noah Roberts
The OP asked explicitly where the perceived slowness comes from and did not seem to be advancing an agenda of *OOP is bad OMG.* The fact remains that many OOP practices, when naively applied, do lead to serious performance issues that are often hard to fix later.
dash-tom-bang
I never said the OP was advancing that agenda. They asked why people think it is. They think so because they're ignorant of the facts and don't care enough to learn the truth.
Noah Roberts
+3  A: 

My thoughts on the subject, since I like the fire.

Proper OO is "slower" than non-OO because it's doing more. However, beyond that you can't really say much. I hesitate to make comparisons between equivalent OO and non-OO programs, because ultimately they would just be doing the same work in different ways.

Ultimately, it comes down to the quality of your programmers. Good programmers understand the costs that come with using their tools. Someone who says that virtual function overhead doesn't matter will not succeed on a team that requires high performance. Someone who says that OO sucks will not thrive on a team that makes heavy use of OO. The question isn't whether the tool is good or bad, but whether it is appropriate for a given problem or not.

As to the edit to the OP: I don't believe that OO on its own makes a piece of software slow or fast. I once reimplemented a buggy piece of crap C program (with many inscrutable sections commented as being "for speed") in partially-OO Python script and gotten a 10x speedup with 10% the number of lines of code. The reason for code being slow is, as I said in the second paragraph, really down to the programmers writing the code. The fact is that a lot of programmers just aren't very good. I personally find that the easiest way to speed up code is just to simplify it, as that exposes the true intent of the code and ultimately the software does less to achieve the same goals.

dash-tom-bang
+2  A: 

On the other hand I've often heard (and read) people arguing that OO programming is slower by design that non-OO programming. And I've heard that especially about C++.

What I wonder is what makes them say so, as it is so different from my personal experience.

... what kind of OO practises could lead to slower C++ code that equivalent programs written using C.

  • encapsulation
  • virtual dispatch

There are two levels at which I'd like to address this question. Firstly, OO vs non-OO, secondly a more general discussion of C vs C++ performance and how it shapes opinions - OO may simply be a tangible thing for C programmers to point at when complaining of C++'s performance.

OO specifically deals with encapsulation, run-time polymorphism (virtual dispatch), and inheritance.

Of these, encapsulation can adversely affect performance by insisting on discrete operations preserving class invariants, whereas a lack of encapsulation lets client code affect an object in a more direct way that may be optimised in light of several operations that will be preformed before the state again meets the invariants. For example, OO programming is more likely to result in unnecessary default initialisation of variables that will later be over-written before being read. The issues mirror those in the OS world: monolithic OS kernels yield higher performance than modules arranged around a micro-kernel, although the latter is typically more stable.

By writing user-defined types (objects) supporting value semantics, programmers are more likely to carelessly copy values around and create temporaries. In C programming the language doesn't provide user-defined operators etc. that so easily lead to the creation of temporary objects.

In C++, implicit constructors and conversion operators also create objects that may typically be avoided in equivalent C code. For example...

void fn(const std::string& s);

...is a convenient interface as you can call it without having to use .c_str() on a std::string, and you can also pass a const char* and have a temporary created. Often, C++ programmers won't bother to create a second void fn(const char*) unless it's really obvious - or proven by profiling - to be significant. All these little things add up though and contribute to the general impression of C++ as being wasteful.

Virtual dispatch forces out-of-line function calls, which - for trivially simple function bodies - can be an order of magnitude slower than inlined calls. Their speed is comparable to explicitly using pointers to functions, but that can still be slower than code that uses switch statements, cascading ifs etc., as is more common in non-OO code. The OO code is more maintainable though.

C++'s OO features also encompass constructors and operators that most naturally use exceptions to report issues (given the former have no return value and setting a state for subsequent testing invites the ignoring of errors, and the latter typically needs - when successful - to return a reference to the current or a resultant object). Exceptions may or may not be less efficient than return codes depending on the hardware, compiler, situation and frequency of throws etc., but with the older compilers that were around when many of the people you say disparage C++'s performance and OO generally formed their opinions.

Considering the bigger picture of C++ vs C performance and not just OO, C++ provides higher level facilities in the STL and other libraries that provide general purpose facilities. In many specific, limited uses a hand-crafted solution may outperform the general solution, although typically only by a small amount. C++ addresses this better than any other language I'm aware of by supporting templated algorithms that are instantiated for each type to which they're applied, allowing inlining, traits-driven, compile-time sizeof and other optimisations. Still, a std::string uses the heap whereas C programs go further out of their way to avoid or minimise that, and C-style heap allocation offers realloc which can substantially outperform a secondary new/copy/delete cycle. Low-cost operations like maintaining container sizes are adopted as standard practice in the STL, but may be unnecessary in equivalent C code. STL heap memory usage tends to be dynamically scaled to run-time usage, whereas C programmers tend to put more effort into avoiding the need for that and may reap consequent performance gains (often at the cost of arbitrary limitations on program capabilities... how many standard UNIX utils written in C have arbitrary hard-limits on line-size etc. on Solaris etc. - extra effort had to be spent to systematically remove such limits from GNU utils).

Crucially, if you pick up any single C library or function that provides equivalent general-purpose higher-level functionality, it's overwhelmingly likely to perform worse than the C++ equivalents (due to lack of templates). Consider the common UNIX C qsort() and bsearch().

Design patterns do tie into this. C++ programmers may be helped towards higher-level conceptualisation of their problems by having more middle-ground provided by the STL (compared to C programmers). Naturally, they are more likely to use that to adopt more formalised, standardised, generic, reusable etc. solutions to common issues at the level of abstraction that allows. Again, generalised solutions stacked on top of generalised solutions tend to perform worse than monolithic integrated solutions. Nobody makes you use these higher-level general-purpose building blocks... if they're inappropriate to your performance needs then don't. But, normally they're fine and time's better spent profiling afterwards and making a few targetted tweaks.

Again, productivity is a core issue here. C++ lets less people get more done (compared to C), while still being able to go as low as necessary to get as much or more performance when needed. In some senses, C++ is a (reputational) victim of its own success: "C++ takes too long to compile"... mainly because it scales to tens of millions of lines of code that the complaintant can't dream of in their newly-beloved Ruby interpreter (though compilation dependencies do need to be actively managed). "C++ is too slow handling associative containers of strings", because something someone spent 2 seconds to implement it in C++ is being contrasted with months of work on a container hand-optimised for the string content involved.

There - I've succeeded in writing an answer as rambling as the question ;-P.

Tony
@Tony: I like your answer and would upvote it several times if I could (to put it nearer to the top, sorry no point, it's community wiki), as it is argumented but not polemic. I will try to add some exemples of code of the kind of simple problems pointed out (at least for virtualization at many people seems to regard this as emblematic).
kriss