28

C++ style vs. performance - is using C-style things, that are faster the some C++ equivalents, that bad practice ? For example:

  • Don't use atoi(), itoa(), atol(), etc. ! Use std::stringstream <- probably sometimes it's better, but always? What's so bad using the C functions? Yep, C-style, not C++, but whatever? This is C++, we're looking for performance all the time..

  • Never use raw pointers, use smart pointers instead - OK, they're really useful, everyone knows that, I know that, I use the all the time and I know how much better they're that raw pointers, but sometimes it's completely safe to use raw pointers.. Why not? "Not C++ style? <- is this enough?

  • Don't use bitwise operations - too C-style? WTH? Why not, when you're sure what you're doing? For example - don't do bitwise exchange of variables ( a ^= b; b ^= a; a ^= b; ) - use standard 3-step exchange. Don't use left-shift for multiplying by two. Etc, etc.. (OK, that's not C++ style vs. C-style, but still "not good practice" )

  • And finally, the most expensive - "Don't use enum-s to return codes, it's too C-style, use exceptions for different errors" ? Why? OK, when we're talking about error handling on deep levels - OK, but why always? What's so wrong with this, for example - when we're talking about a function, that returns different error codes and when the error handling will be implemented only in the function, that calls the first one? I mean - no need to pass the error codes on a upper level. Exceptions are rather slow and they're exceptions for exceptional situations, not for .. beauty.

  • etc., etc., etc.

Okay, I know that good coding style is very, very important <- the code should be easy to read and understand. I know that there's no need from micro optimizations, as the modern compilers are very smart and Compiler optimizations are very powerful. But I also know how expensive is the exceptions handling, how (some) smart_pointers are implemented, and that there's no need from smart_ptr all the time.. I know that, for example, atoi is not that "safe" as std::stringstream is, but still.. What about performance?


EDIT: I'm not talking about some really hard things, that are only C-style specific. I mean - don't wonder to use function pointers or virtual methods and these kind of stuff, that a C++ programmer may not know, if never used such things (while C programmers do this all the time). I'm talking about some more common and easy things, such as in the examples.

Kiril Kirov
  • 37,467
  • 22
  • 115
  • 187
  • 16
    Unless you're working on hard real-time systems, programmer time is much more valuable than CPU time. – Ignacio Vazquez-Abrams Nov 13 '10 at 09:36
  • 2
    @Ignacio Vazquez-Abrams : Currently, I'm working exactly on real-time systems, processing lots of messages/traffic per second, but still I'm told all the time - "don't do this, it's c-style" .. Also, you're completely right about programmer's time, I agree with you, but using `atoi` (for example) doesn't take more time that using `std::stringstream`. – Kiril Kirov Nov 13 '10 at 09:40
  • 2
    @Ignacio, and I would also proffer if you're working on a game where every tick counts. – Moo-Juice Nov 13 '10 at 09:41
  • 4
    Even in the most time critical application micro-optmisations like these rarely make an measureable difference, it's almost always use of the right algorithms and data structures that makes the difference. And increasingly these days with multi core systems correct and efficient use of locking, and writing cache friendly code – jcoder Nov 13 '10 at 09:55
  • 1
    @Moo-Juice: that's a common misconception. Not every tick counts in a game. *Some* ticks count, but others don't. A game contains plenty of non-performance-critical code, just like any other app. And just like any other app, it is important to identify the 10% that really really needs to be optimized. – jalf Nov 13 '10 at 13:52
  • 3
    Just a case study for you. I also work on real-time systems. My coworkers often rib me for my extensive use of "space-age" C++ constructs, like templates, exceptions, stringstream, etc. But when my code is compared to similar code written in the C-style with a mind toward efficiency, my code is usually faster. Even with no micro-optimizations. And my code almost never comes back to me from Q/A because it *works* -- both the first time, and later when things change. Highly "optimized" C-code can't say that. It's brittle and doesn't like change. – John Dibling Nov 13 '10 at 18:55

10 Answers10

46

In general, the thing you're missing is that the C way often isn't faster. It just looks more like a hack, and people often think hacks are faster.

Never use raw pointers, use smart pointers instead - OK, they're really useful, everyone knows that, I know that, I use the all the time and I know how much better they're that raw pointers, but sometimes it's completely safe to use raw pointers.. Why not?

Let's turn the question on its head. Sometimes it's safe to use raw pointers. Is that alone a reason to use them? Is there anything about raw pointers that is actually superior to smart pointers? It depends. Some smart pointer types are slower than raw pointers. Others aren't. What is the performance rationale for using a raw pointer over a std::unique_ptr or a boost::scoped_ptr? Neither of them have any overhead, they just provide safer semantics.

This isn't to say that you should never use raw pointers. Just that you shouldn't do it just because you think you need performance, or just because "it seems safe". Do it when you need to represent something that smart pointers can't. As a rule of thumb, use pointers to point to things, and smart pointers to take ownership of things. But it's a rule of thumb, not a universal rule. Use whichever fits the task at hand. But don't blindly assume that raw pointers will be faster. And when you use smart pointers, be sure you are familiar with them all. Too many people just use shared_ptr for everything, and that is just awful, both in terms of performance and the very vague shared ownership semantics you end up applying to everything.

Don't use bitwise operations - too C-style? WTH? Why not, when you're sure what you're doing? For example - don't do bitwise exchange of variables ( a ^= b; b ^= a; a ^= b; ) - use standard 3-step exchange. Don't use left-shift for multiplying by two. Etc, etc.. (OK, that's not C++ style vs. C-style, but still "not good practice" )

That one is correct. And the reason is "it's faster". Bitwise exchange is problematic in many ways:

  • it is slower on a modern CPU
  • it is more subtle and easier to get wrong
  • it works with a very limited set of types

And when multiplying by two, multiply by two. The compiler knows about this trick, and will apply it if it is faster. And once again, shifting has many of the same problems. It may, in this case, be faster (which is why the compiler will do it for you), but it is still easier to get wrong, and it works with a limited set of types. In paticular, it might compile fine with types that you think it is safe to do this trick with... And then blow up in practice. In particular, bit shifting on negative values is a minefield. Let the compiler navigate it for you.

Incidentally, this has nothing to do with "C style". The exact same advice applies in C. In C, a regular swap is still faster than the bitwise hack, and bitshifting instead of a multiply will still be done by the compiler if it is valid and if it is faster.

But as a programmer, you should use bitwise operations for one thing only: to do bitwise manipulation of integers. You've already got a multiplication operator, so use that when you want to multiply. And you've also got a std::swap function. Use that if you want to swap two values. One of the most important tricks when optimizing is, perhaps surprisingly, to write readable, meaningful code. That allows your compiler to understand the code and optimize it. std::swap can be specialized to do the most efficient exchange for the particular type it's used on. And the compiler knows several ways to implement multiplication, and will pick the fastest one depending on circumstance... If you tell it to. If you tell it to bit shift instead, you're just misleading it. Tell it to multiply, and it will give you the fastest multiply it has.

And finally, the most expensive - "Don't use enum-s to return codes, it's too C-style, use exceptions for different errors" ?

Depends on who you ask. Most C++ programmers I know of find room for both. But keep in mind that one unfortunate thing about return codes is that they're easily ignored. If that is unacceptable, then perhaps you should prefer an exception in this case. Another point is that RAII works better together with exceptions, and a C++ programmer should definitely use RAII wherever possible. Unfortunately, because constructors can't return error codes, exceptions are often the only way to indicate errors.

but still.. What about performance?

What about it? Any decent C programmer would be happy to tell you not to optimize prematurely.

Your CPU can execute perhaps 8 billion instructions per second. If you make two calls to a std::stringstream in that second, is that going to make a measurable dent in the budget?

You can't predict performance. You can't make up a coding guideline that will result in fast code. Even if you never throw a single exception, and never ever use stringstream, your code still won't automatically be fast. If you try to optimize while you write the code, then you're going to spend 90% of the effort optimizing the 90% of the code that is hardly ever executed. In order to get a measurable improvement, you need to focus on the 10% of the code that make up 95% of the execution time. Trying to make everything fast just results in a lot of wasted time with little to show for it, and a much uglier code base.

jalf
  • 243,077
  • 51
  • 345
  • 550
  • 6
    +1 for "Smart Pointers Are Not Created Equal" and of course all the rest. – rubenvb Nov 13 '10 at 13:49
  • Thanks a lot for your complete answer! Accepted and noted – Kiril Kirov Nov 13 '10 at 13:55
  • A good post. Some remarks: std::swap exists for swapping in the same way that boost::lexical_cast exists for simple conversions - I sincerely trust the Boost Wizards to convert my strings in the most efficient way, be it a stringstream or C functions or some hackage, I don't care what inside. Also, the last paragraph definitely should be the first one. :) – Kos Nov 15 '10 at 17:26
  • "one unfortunate thing about return codes is that they're easily ignored" -- unfortunately, C++ exceptions are also easily ignored... and while an ignored return code may or may not have bad consequences, an unhandled exception will have fatal consequences. – Jeremy Friesner Mar 31 '13 at 22:15
  • @JeremyFriesner - sometimes it's much, much, **much** better to have "fatal consequences". And these "sometimes" are not rare. – Kiril Kirov Apr 18 '13 at 10:35
  • @jalf: "Neither of them have any overhead, they just provide safer semantics". Really? Don't they have destructors that are being invoked at the end of scope whereas you would deallocate a raw pointer manually sooner? If they're keeping references alive to the end of scope then that will increase register pressure and degrade performance. If you `null` out the smart pointer before the end of scope then is that really any better than using a raw pointer? – J D Oct 28 '13 at 08:10
  • "it is slower on a modern CPU". Is that true on ARM CPUs? – J D Oct 28 '13 at 08:14
  • 1
    @JonHarrop: yes, it is true for ARM CPUs as well. As for the overhead and destructors, why do you think "the destructor calls delete" is going to be slower than "I manually call delete"? You are right that *if* you delete the object sooner, then that may slightly lessen register pressure and so that might very slightly improve performance. But that has nothing to do with smart pointers. Changing the semantics of your program is always going to affect performance. If the semantics (and order of deletion) doesn't change, then there's no overhead to using a smart pointer vs a raw pointer – jalf Oct 28 '13 at 08:56
  • And yes, I think manually resetting a smart pointer before it goes out of scope is a lot better than using a raw pointer, from a safety point of view: you still have the guarantee that the object won't outlive the smart pointer's scope. You can delete it earlier, but even if you forget, it won't be leaked. – jalf Oct 28 '13 at 09:03
  • @jalf: "that might very slightly improve performance". Would be interesting to quantify that using a benchmark. – J D Oct 29 '13 at 17:05
14
  1. I'd advise against atoi, and atol as a rule, but not just on style grounds. They make it essentially impossible to detect input errors. While a stringstream can do the same job, strtol (for one example) is what I'd usually advise as the direct replacement.
  2. I'm not sure who's giving that advice. Use smart pointers when they're helpful, but when they're not, there's nothing wrong with using a raw pointer.
  3. I really have no idea who thinks it's "not good practice" to use bitwise operators in C++. Unless there were some specific conditions attached to that advice, I'd say it was just plain wrong.
  4. This one depends heavily on where you draw the line between an exceptional input, and (for example) an input that's expected, but not usable. Generally speaking, if you're accepting input direct from the user, you can't (and shouldn't) classify anything as truly exceptional. The main good point of exceptions (even in a situation like this) is ensuring that errors aren't just ignored. OTOH, I don't think that's always the sole criterion, so you can't say it's the right way to handle every situation.

All in all, it sounds to me like you've gotten some advice that's dogmatic to the point of ignoring reality. It's probably best ignored or at least viewed as one rather extreme position about how C++ could be written, not necessarily how it always (or ever, necessarily) should be written.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • +1 for the raw pointers. (Yep, love them, use them, I'm smart, my computer is, but I like to think I'm smarter even though it's probably wrong). – Raveline Nov 13 '10 at 10:26
  • I love the raw pointers, too, they're great, when you know what you're doing (: – Kiril Kirov Nov 13 '10 at 10:42
  • On point 1. There are situations were you don't need to detect errors on input (an error is the same as 0). – Patrick Schlüter Nov 13 '10 at 10:50
  • As for the OP's point 3, I think the extrapolation to all bitwise operators is in error, not the 2 specific cases he provided, because they are indeed problematic. The xor-swap is retarded, it's much slower than with a temp variable and works only for 2 complements. The shift by 2 instead of multiply/divide is bad because the behaviour of signed arguments is different and unportable. But that doesn't mean that all bit operators should be avoided. – Patrick Schlüter Nov 13 '10 at 10:56
  • About 2 and 3, the OP is under the misconception that those things are a *faster*. Several smart pointer types are basically zero-overhead, and using the bitwise hack to exchange two variables is *slower* than just doing it normally. – jalf Nov 13 '10 at 13:22
  • 1
    given the specific examples used in #3, I think nearly everyone would agree it's not good practice to use them. – jalf Nov 13 '10 at 13:48
9

Adding to @Jerry Coffin's answer, which I think is extremely useful, I would like to present some subjective observations.

  • The thing is that programmers tend to get fancy. That is, most of us really like writing fancy code just for the sake of it. This is perfectly fine as long as you are doing the project on your own. Remember a good software is the one whose binary code works as expected and not the one whose source code is clean. However when it comes to larger projects which are developed and maintained by lots of people, it is economically better to write simpler code so that no one from the team loses time to understand what you meant. Even at the cost of runtime(naturally minor cost). That's why many people, including myself, would discourage using the xor trick instead of assignment(you may be surprised but there are extremely many programmers out there that haven't heard of the xor trick). The xor trick works only for integers anyway, and the traditional way of swapping integers is very fast anyway, so using the xor trick is just being fancy.

  • using itoa, atoi etc instead of streams is faster. Yes, it is. But how much faster? Not much. Unless most of your program does only conversions from text to string and vice versa you won't notice the difference. Why do people use itoa, atoi etc? Well, some of them do, because they are unaware of the c++ alternative. Another group does because it's just one LOC. For the former group - shame on you, for the latter - why not boost::lexical_cast?

  • exceptions... ah ... yeah, they can be slower than return codes but in most cases not really. Return codes can contain information, which is not an error. Exceptions should be used to report severe errors, ones which cannot be ignored. Some people forget about this and use exceptions for simulating some weird signal/slot mechanisms (believe me, I have seen it, yuck). My personal opinion is that there is nothing wrong using return codes, but severe errors should be reported with exceptions, unless the profiler has shown that refraining from them would considerably boost the performance

  • raw pointers - My own opinion is this: never use smart pointers when it's not about ownership. Always use smart pointers when it's about ownership. Naturally with some exceptions.

  • bit-shifting instead of multiplication by powers of two. This, I believe, is a classic example of premature optimization. x << 3; I bet at least 25% of your co-workers will need some time before they will understand/realize this means x * 8; Obfuscated (at least for 25%) code for which exact reasons? Again, if the profiler tells you this is the bottleneck (which I doubt will be the case for extremely rare cases), then green light, go ahead and do it (leaving a comment that in fact this means x * 8)

To sum it up. A good professional acknowledges the "good styles", understands why and when they are good, and rightfully makes exceptions because he knows what he's doing. Average/bad professionals are categorized into 2 types: first type doesn't acknowledge good style, doesn't even understand what and why it is. fire them. The other type treats the style as a dogma, which is not always good.

Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • 5
    C++ streams really are one of my pet peeves. About speed of atoi vs. stringstreams. You write "How much faster? Not much." Typically the C variants are at least an order of magnitude faster. So trust me, it can matter. Another drawback is compile times - suddenly the compiler has to pull in and parse hundreds of kilobytes of template code, which can matter in a large project. To add insult to injury, the equivalent C++ code typically is more complicated aswell. – Johan Kotlinski Nov 13 '10 at 10:50
  • 1
    @kotlinski: The equivalent C++ code is more complicated for those who treat C++ as C with some fancy features. IMHO – Armen Tsirunyan Nov 13 '10 at 11:13
  • @Oli, @kotlinski: if by verbose you mean it has more characters in it, then yes. But for me personally it takes a while to figure out that itoa means Integer TO Alphabetical. One could argue that printf and scanf are better because they are more intuitive... Extremely personal, I think. – Armen Tsirunyan Nov 13 '10 at 11:35
  • 1
    @kotlinski: The question is how much that order of magnitude in speed while parsing data from strings to c++ types takes in the whole program execution time. In most cases one order of magnitude slower there will not even affect the overall performance. In those cases, if using the slower version helps guaranteeing that the input is correct, then it is worth it. Remember: `He who puts performance over correctness deserves neither`. Would you rather have the software in a surgery room be able to run in an old pentium with just some minor failures, or require a top of the line i7 and be correct? – David Rodríguez - dribeas Nov 13 '10 at 11:49
  • @David: 1) Imagine a real-time application where a lot of numbers has to be parsed in a fraction of a second. Then it matters. 2) I don't buy the correctness argument. After all, there is a lot of working C code out there. – Johan Kotlinski Nov 13 '10 at 12:43
  • 1
    @Armen: The complexity problem is not just a matter of the syntax and semantics of the user code. The bigger problem may be in the forced complexity of the C++ stringstream implementation, which often translates to ineffective code. – Johan Kotlinski Nov 13 '10 at 12:46
  • 1
    @kotlinsky: I'd say the amount of *non-working* C code out there is more relevant. And there's a lot of that too. So the correctness argument isn't so far out. (Which isn't to say C++ IOStreams aren't a ridiculous monstrosity. It's just a monstrosity that makes it harder to write incorrect code) – jalf Nov 13 '10 at 15:06
  • @kotlinksi: Your comment is spot on. – Matt Joiner Nov 15 '10 at 01:46
3

What's a best practice ? Wikipedia's words are better than mine would be :

A best practice is a technique, method, process, activity, incentive, or reward which conventional wisdom regards as more effective at delivering a particular outcome than any other technique, method, process, etc. when applied to a particular condition or circumstance.

[...]

A given best practice is only applicable to particular condition or circumstance and may have to be modified or adapted for similar circumstances. In addition, a "best" practice can evolve to become better as improvements are discovered.

I believe there is no such thing as universal truth in programming : if you think that something is a better fit in your situation than a so called "best practice", then do what you believe is right, but know perfectly why you do (ie: prove it with numbers).

icecrime
  • 74,451
  • 13
  • 99
  • 111
3
  1. Functions with mutable char* arguments are bad in C++ because it's too difficult to manually handle their memory, since we have an alternatives. They aren't generic, we can't easily switch from char to wchar_t as basic_string allows. Also lexical_cast is more straight replacement for atoi, itoa.
  2. If you don't really need smartness of a smart pointer - don't use it.
  3. To swap use swap. Use bitwise operations only for bitwise operations - checking/setting/inverting flags, etc.
  4. Exceptions are fast. They allow removing error checking condition branches, so if they really "never happen" - they increase performance.
Mike Dunlavey
  • 40,059
  • 14
  • 91
  • 135
Abyx
  • 12,345
  • 5
  • 44
  • 76
2

Multiplication by bitshifting doesn't improve performance in C, the compiler will do that for you. Just be sure to multiply or divide by 2^n values for performance.

Bitfield swapping is also something that'll probably just confuse your compiler.

I'm not very experienced with string handling in C++, but from from what I know, it's hard to believe it's more flexible than scanf and printf.

Also, these "you should never" statements, I generally regard them as recommendations.

onemasse
  • 6,514
  • 8
  • 32
  • 37
  • Well, string handling in C++ is a lot more readable to the naked eye than tens of format specifiers in a `printf` line. Please don't judge what you don't know. `stringstream` s are equally flexible (if not more, due to the possibility of subclassing the `streambuf` class) than `scanf`, and perform their task very well. – rubenvb Nov 13 '10 at 11:44
  • 2
    @rubenvb: I was going to provide an comparing how excessively verbose the iostreams formatting syntax is compared with printf was but after 5 minutes of trying to understand it and about 6 lines of code I gave up. – Matt Joiner Nov 15 '10 at 01:56
  • @Matt: I'm with you there. As far as I can see, if iostreams have an advantage, it is they don't have the possible security holes of `printf` interpreting a format string. – Mike Dunlavey Nov 15 '10 at 15:15
  • @kotlinski: Didn't know that. Thanks. Though there's still the possibility of format strings not known until run time. – Mike Dunlavey Nov 15 '10 at 17:07
2

All of your questions are a-priori. What I mean is you are asking them in the abstract, not in the context of any specific program whose performance is your concern. That's like trying to swim without being in water.

If you do tuning on a specific concrete program, you will find performance problems, and chances are they will have almost nothing whatever to do with these abstract questions. They will most likely all be things you could not have thought of a-priori.

For a specific example of this, look here.

If I could generalize from experience, a major source of performance problems is galloping generality. That is, while data structure abstraction is generally considered a good thing, any good thing can be massively over-used, and then it becomes a crippling bad thing. This is not rare. In my experience it is typical.

Community
  • 1
  • 1
Mike Dunlavey
  • 40,059
  • 14
  • 91
  • 135
1

I think you're answering big parts of your question on your own. I personally prefer easy-to-read code (even if you understand C style, maybe the next to read your code has more trouble with it) and safe code (which suggests stringstream, exceptions, smart pointers...)

If you really have something where it makes sense to consider bitwise operations - ok. But often I see C programmers use a char instead of a couple of bools. I do NOT like this.

Speed is important, but most of the time is usually required at a few hotspots in a program. So unless you measure that some technique is a problem (or you know pretty sure that it will become one) I would rather use what you call C++ style.

Philipp
  • 11,549
  • 8
  • 66
  • 126
  • Thanks for the comment. Yep, I've answered some of the questions, but I don't really know which one is better and I need to hear other people's opinions. Also, I don't mean something really, really ugly and difficult to understand. None of the given examples are hard to unterstand, no matter if the programmer after me knows C-style things, or not. For example, the error handling I mentioned - every good programmer will understand it. Also, if someone doesn't know what `atoi` is, there's google. I'm not talking about some really C-specific things. I'll edit my post a little. – Kiril Kirov Nov 13 '10 at 09:52
  • well, style is a question of personal preference and if both C and C++ are appropriate to solve your problem you can choose among them. But I still feel like exceptions are safer than return codes (which often are forgotten to check and hard to pass on). – Philipp Nov 13 '10 at 09:55
  • exceptions, are well how do you say, exceptions, return codes aren't they return codes are the opposite of exceptions, they are normal. They shouldn't be used interchangeably. – hhafez Nov 13 '10 at 10:10
  • "forgotten to check and hard to pass" - yep, that's why I specified the level of depth - I mean not to pass the error code more that one or two levels up (to caller functions), because it's really hard to pass and check then. – Kiril Kirov Nov 13 '10 at 10:11
1

Why the expensiveness of exceptions is an argument? Exceptions are exceptions because they are rare. Their performance doesn't influence the overall performance. The steps you have to take to make your code exception-safe do not influence the performance either. But on the other hand exceptions are convenient and flexible.

facetus
  • 1,091
  • 6
  • 20
  • yes, they are "convenient and flexible", and yes, I said: "they're exceptions for exceptional situations, not for .. beauty", but why not using enum-s and different return codes, but to use an hierarchy of exceptions, when this can be avoided. Is that such bad practice. – Kiril Kirov Nov 13 '10 at 09:58
  • 1
    It really depends on the situation, whether exceptions make things easier or not. For some applications, there should be no errors and no exceptional behavior. Instead of throwing an exception, it may be more useful to have an assert so you can check the callstack. Asserts also can be easily disabled when it is time for the final release. – Johan Kotlinski Nov 13 '10 at 10:08
  • In fact in most cases in a well designed application there should be assertions as well as exceptions handling. In most cases return codes is not a bad practice per se, it is just less flexible and less convenient. There are a few cases where exceptions are unacceptable. For instance, exceptions must not cross the boundaries of different binary components. That is why COM conventions prohibit exceptions cross COM methods boundaries. – facetus Nov 14 '10 at 05:03
1

This is not really an "answer", but if you work in a project where performance is important (e.g. embedded/games), people usually do the faster C way instead of the slower C++ way in the ways you described.

The exception may be bitwise operations, where not as much is gained as you might think. For example, "Don't use left-shift for multiplying by two." A half-way decent compiler will generate the same code for << 2 and * 2.

Johan Kotlinski
  • 25,185
  • 9
  • 78
  • 101