1

This question already has an answer here:

As a rookie programmer, I've only recently been digging into benchmarking, and have been comparing various ways of accomplishing the same task in terms of speed.

Most of the time, the result is either that the difference in speed is negligible or that it is so huge that I'm definitely going to use the faster option.

That leaves the cases in the middle. If I understand correctly from reading similar questions and answers, the consensus of software engineers is that any optimizations that clobber the readability of your code or make you expend valuable time should be left until you've run into a performance issue and identified what code is the bottleneck. That seems entirely reasonable to me.

However, what is "readable" and what is not? I have a very concrete example in mind. Take a look at this benchmark in JavaScript: https://jsperf.com/cond-vs-mult. It compares the use of implicit conditioning through multiplication with a boolean value with an explicit conditional by using the ternary operator ?. Now, it costs me no time or effort to simply write the faster version in all cases, so the only concern in my mind is readability.


My opinion after reading comments & answers

I don't agree with the duplication vote. I understand how this is remarkably similar to the linked question, but this was mainly about the readability of an implied conditional. Can you answer the linked question with an answer such as "the ternary operator is much easier to read than addition by a product with a boolean value"? No, but you can this one.

There could - but has not yet been - an underlying answer to both questions. This would have to invoke the idea of signaling, as done by keywords such as if or nearly universal functions such as strlen. Straying away from any such universal methods of doing something towards a language-specific or even implementation-specific method is bound to be less readable, thus constituting a micro-optimization, on which the consensus is clear. However, as it stands, the answers have only concluded the ternary operator is more readable and have thus have been unique to this question. The underlying discussion about micro-optimization, as I tried to make clear by mentioning this in my question, was not the main topic of the question. I will adjust the wording of the question to highlight this. So I really don't consider my question a duplication to the linked one.

marked as duplicate by gnat, Pieter B, Greg Burghardt, Bart van Ingen Schenau, Derek Elkins Sep 29 '17 at 21:39

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • Your explicit example isn't actually testing valid options, though. The ternary operator will return 5 if bool1 is true, and 10 if it's false. The multiplication version will return 10 if it's true and 0 if it's false. – Paul Sep 29 '17 at 12:03
  • 1
    which is why you shouldn't write your code in an obscure manner – Nick Keighley Sep 29 '17 at 12:25
  • @Paul: I'm pretty sure the tests are written in pairs. 1 and 2 are functionally equivalent (when looking at their output), as are 3 and 4. There's no reason to think that these pairs are supposed to match eachother, as the latter pair completely omits num1. – Flater Sep 29 '17 at 12:41
  • 3
    I see you easily reach a billion ops/sec. Once you start going in to programming where you actually need those 100 million calculations your question is largely irrelevant. Once you do start going there read up on your algorithms and datastructures. – Pieter B Sep 29 '17 at 13:10
  • In addition to the comment by @PieterB when I ran the benchmark I only saw a 1.6% difference and an error spread of 1.5% so for all intents and purposes I would consider that the performance is equal. – Peter M Sep 29 '17 at 15:29
3

I believe you have answered your own question. You state that it is reasonable to prefer readability over performance unless there is a performance problem. You also (implicitly) state that the faster method in your example is faster and less readable. Therefore, only use the faster, less-readable method if performance is an issue. If it isn't, slow(er)1 and readable is the way to go.


1 Let's be honest. The difference here is pretty negligible, especially in day to day code. Unless you are processing a ton of things, it won't make a difference. And if you are, well then performance probably matters and therefore using the faster method is justified.

4

Sweeping the dust under the rug

When you're dealing with micro-optimizations in order to improve your program speed (rewriting O(1) operations to outperform their traditional counterparts), there is something else that seriously needs to be addressed about your program more often than not. You can't fix up a house about to collapse in on itself by sweeping the dust under the rug. In other words, if your program is fast already, micro-optimizations can improve it further, but to no great benefit and if your program is slow, micro-optimizations ain't the problem. At the very least, you shouldn't be able to justify making your program less readable at the expense of micro-optimizations because of the little benefit you stand to gain by doing it.

Except...

Except what? You just said you shouldn't be able to justify making micro-optimizations. Yes, while this is true, there are edge cases when the micro-optimizations such as when your little insignificant O(1) operation is wrapped in an unavoidable nested loop being called at every draw frame. If you do the math, for something that's potentially being called 100,000 times a second, that makes that little O(1) operation that took 1 microsecond suddenly take a tenth of a second instead. Perform five operations like this and your frame rate dropped from fluid 60 frames per second to only 2.

Conclusion

Therefore, if I see you optimizing speed-sensitive portions of your code, I'll try not to judge too harshly. Though it would be helpful to remember that you can always comment on your code either with the more readable version of that line or a brief explanation of what it accomplishes. There are also other ways speed up your code such as using threads, if there is work that can be accomplished in parallel. Just don't perform micro-optimizations unless you can genuinely justify such an optimization in terms of time saved in your program that runs it.

  • 1
    So true. A month or so ago I rewrote a part of one of our components to go from O(n^3) to O(3n) so really O(n) and people were surprised that we got a an almost 40% performance boost. We had people try to optimize this before with crazy micro optimizations which gained us a percent here or there and made the entire algorithm nearly impossible to understand, but nobody ever bothered to check the fact that we just needed to iterate over the input three times sequentially instead of three nested times... – Hangman4358 Sep 30 '17 at 2:50
1

However, what is "readable" and what is not?

That's very subjective. Readability is not a binary trait, it's a spectrum. We can't conclusively call something (un)readable without also defining a frame of reference.

I agree that bool1 ? num1 : num2 is more readable than bool1 * num1 + !bool1 * num2. It's much easier to deduce the intention of the code when you see a ternary operator, compared to a contrived mathematical equation which ends up working exactly like an if statement.

I agree that developers should strive to keep their code as readable as possible. However, this does not mean that you should therefore use the readable version over the performant one.

Why do we avoid unreadable code?

Because we can't easily deduce the intention of the code.

How can we avoid unreadable code?

There are two different approaches here:

1. Change the code

This is often the best solution, because developers first try to "quick assess" the code, i.e. try to understand the intention from its basic structure.
E.g. When glossing over code, I'm able to spot an if/else block long before I've actually read which values are being evaluated and returned.

This applies not only to code structure, but also the operators that are being used. Like I said before, I agree that bool1 ? num1 : num2 is more readable than bool1 * num1 + !bool1 * num2, because the ternary operator quickly reveals that this code is trying to function like an if/else block.

However, if there are meaningful performance differences, where a more readable variant is notably less performant than its less readable counterpart, we should consider option 2:

2. Make the existing code more readable but functionally unchanged.

This can be done through renaming variables, e.g. turning c = b * a into surfaceArea = width * height or totalPrice = unitCost * unitQuantity. This is the same operation, it cannot possible be less performant than its more terse counterpart, but it's still considerably more readable for a developer.

But another way to do is this through commenting. Comments are a great tool here, because they allow you to add information for a developer without impacting the flow/performance of the code at runtime.

res = bool1 * num1 + !bool1 * num2;
    //this is the functional equivalent of "bool1 ? num1 : num2"
    //but its performance is significantly higher

Problem solved! The code is focusing on performance, but the comments enhance the readability of an otherwise fairly unreadable code snippet.

Now we have the best of both worlds.


When I make an improvement to my code based on something I've found online, I'm also likely to add the URL:

res = bool1 * num1 + !bool1 * num2;
    //this is the functional equivalent of "bool1 ? num1 : num2"
    //but its performance is significantly higher
    //See: https://jsperf.com/cond-vs-mult

There are a fair amount of MSDN/StackOverflow links in my code (or references to e.g. the functional analysis), simply because it's the easiest way to provide information without having to write it all in comments.

  • I would argue that your code comments are misleading in this particular example. I ran the benchmarks referenced by the OP and only came up with a 1.6% difference between the two examples and that difference is overlapped by the suggested error range. So if the OP is seeing widely different values I would posit that something else is going on. – Peter M Sep 29 '17 at 15:25
  • @PeterM: It's an example, my answer focuses on the principle of the thing. My answer only really applies in cases where there is a meaningful performance difference in favor of the less readable version (but using it would compromise code readability). In all other situations, it should be obvious that the readable version is better to use, as these other situations inherently entail that the readable version is either equally or more performant than the less readable version, which means there's no good reason to use the less readable version. – Flater Sep 29 '17 at 15:29
  • I know what you trying to do .. but you are illustrating it with a falsehood. – Peter M Sep 29 '17 at 15:32
  • @PeterM: The question isn't whether or not this particular Javascript snippet is more performant than the other snippet. The question is about what to do if a less readable snippet is meaningfully more performant than a more readable variation. The assumption is in the premise of the question. My code examples illustrate the approach. – Flater Sep 29 '17 at 15:34
  • @PeterM: Also note that my answer begins with stating the subjective nature of what constitutes a meaningful performance difference. If there is no meaningful performance difference to begin with, then the OP's question is inherently moot. – Flater Sep 29 '17 at 15:41
0

Your question isn't really going to have a concrete answer, because "readability" is based on an individual style. For me personally, I would use the ternary in the instance you highlighted above merely because bool1 is a boolean and I like using it in a true/false test vs multiplying it. if the variable name was not as descript as you made it in your example, say a lazy programmer just called it "a", then I would assume I was multiplying two numbers and would have to look further in code to see that it in fact is a boolean. But that's just me.

Not the answer you're looking for? Browse other questions tagged or ask your own question.