74

When I write code I always try to make my code as clean and readable as possible.

Every now and then there comes a time when you need to cross the line and go from nice clean code to slightly uglier code to make it faster.

When is it OK to cross that line?

8
  • 72
    You answered your own question, you cross the line when you need to cross the line
    – gnibbler
    Jul 5, 2011 at 1:32
  • 6
    Also, your "dirty code" may work just as fast as "clean code" on hardware 6 months from now. Don't go overboard like Windows did, though. :) Jul 5, 2011 at 6:32
  • 23
    There is a significant difference between a difficult-to-understand algorithm, and difficult-to-understand code. Sometimes the algorithm you need to implement is complicated, and the code will necessarily be confusing, simply because it's expressing a complex idea. But if the code itself is the difficult point, then the code should be fixed.
    – tylerl
    Jul 5, 2011 at 7:25
  • 8
    In a lot of cases a smart compiler / interpreter can optimise clean, readable code so it has the same performance as "ugly" code. So there's little excuse, unless profiling says otherwise.
    – Dan Diplo
    Jul 5, 2011 at 8:27
  • 1
    When it comes to compilers these days, your ugly code will most likely be the same as your clean code (assuming you don't do any really weird stuff). Especially in .NET, it isn't like C++/MFC days where how you define your variables will have an impact on performance. Write maintainable code. some code will just end up being complex but that doesn't mean it's ugly. Jul 7, 2011 at 14:37

10 Answers 10

125

You cross the line when

  • You have measured that your code is too slow for its intended use.
  • You have tried alternative improvements that don't require mucking up the code.

Here's a real-world example: an experimental system I am running was producing data too slowly, taking over 9 hours per run and using only 40% of CPU. Rather than mess up the code too much, I moved all the temporary files to an in-memory filesystem. Added 8 new lines of non-ugly code, and now CPU utilization is above 98%. Problem solved; no ugliness required.

5
  • 2
    You also make sure you keep the original, slower, cleaner code, both as a reference implementation and to fall back on when the hardware changes and your faster, hackier code no longer works.
    – Paul R
    Jul 5, 2011 at 7:05
  • 5
    @PaulR How do you keep that code ? In form of comments ? That is wrong, imo - comments get outdated, nobody reads them, and personally if I see commented out code I usually remove it - this is what source control is for. A comment on a method explaining what it does is better, imo.
    – Evgeni
    Oct 9, 2013 at 14:29
  • 5
    @Eugene: I usually keep the original version of a routine named foo and rename it foo_ref - typically it lives immediately above foo in the source file. In my test harness I call foo and foo_ref for validation and relative performance measurement.
    – Paul R
    Oct 9, 2013 at 14:52
  • 5
    @Paul if you are doing that might be a good idea to fail the test if the optimized version is ever slower than the ref function. this can happen if the assumptions you made to make it go faster are no longer true. Nov 16, 2014 at 18:18
  • @Evgeni: I would separate code interface from its implementation. Two different implementations for the same interface (.h/.c, interface/class, it depends on the language you're using). Then switching from first impl. to second should be a matter of relinking or rerunning your code. No rename involved, more clean to me. Oct 9, 2020 at 10:18
60

It's a false dichotomy. You can make code fast and easy to maintain.

The way you do it is write it clean, especially with as simple a data structure as possible.

Then you find out where the time drains are (by running it, after you've written it, not before), and fix them one by one. (Here's an example.)

Added: We always hear about tradeoffs, right, such as a tradeoff between time and memory, or a tradeoff between speed and maintainability? While such curves may well exist, it should not be assumed that any given program is on the curve, or even anywhere near it.

Any program that is on the curve can easily (by giving it to a certain kind of programmer) be made both much slower, and much less maintainable, and then it will be nowhere near the curve. Such a program then has plenty of room to be made both faster and more maintainable.

In my experience, that's where lots of programs start out.

10
  • I agree. Indeed fast code that is not clean is going to get slower eventually as you can't properly modify it. Jul 5, 2011 at 6:01
  • 26
    I do not agree it is a false dichotomy; IMO there are scenarios especially in library code (not so much in application code) where the split is very real. See my answer for more. Jul 5, 2011 at 6:18
  • 1
    Marc, you can link to answers in comments with the "link" URL. programmers.stackexchange.com/questions/89620/…
    – user1249
    Jul 5, 2011 at 7:30
  • We have all found times that we need to try and make the code go faster. But after experimentation with the profiler to find the best solution (were the code gets ugly) this does not mean the code needs to stay ugly. It is a matter of finding the best solution which may not at first seem obvious but once found can usually be coded cleanly. Thus I believe it is a false dichotomy and just an excuse not to tidy your room after you have had the fun with your toys. I say suck it up and tidy your room. Jul 5, 2011 at 7:33
  • I disagree that it's a false dichotomy. Because I do a lot of graphics work the most obvious example to me is in tight graphics loops: I don't know how often this is still done, but it used to be common for game engines written in C to use Assembly for core rendering loops in order to squeeze out every last drop of speed. That also makes me think of situations where you program in Python but use modules written in C++. "Hard to read" is always relative; any time you drop to a lower-level language for speed, that code is harder to read than the rest.
    – jhocking
    Jul 5, 2011 at 13:08
33

In my OSS existence I do a lot of library work aimed at performance, that is deeply tied to the caller's data-structure (i.e. external to the library), with (by design) no mandate over the incoming types. Here, the best way to make this performant is meta-programming, which (since I'm in .NET-land) means IL-emit. That is some ugly, ugly code, but very fast.

In this way, I happily accept library code may be "uglier" than application code, simply becuase it has less (or maybe no) control over the inputs, so needs to achieve some tasks through different mechanisms. Or as I expressed it the other day:

"coding over the cliff of insanity, so you don't have to "

Now application code is slightly different, as that is where "regular" (sane) developers are typically investing much of their collaborative/professional time; the goals and expectations of each are (IMO) slightly different.

IMO, the answers above that suggest it can be fast and easy to maintain are referring to application code where the developer has more control over the data-structures, and isn't using tools like meta-programming. That said, there are different ways of doing meta-programming, with different levels of insanity and different levels of overhead. Even in that arena you need to choose the appropriate level of abstraction. But when you actively, positively, genuinely want it to handle unexpected data in the absolute fastest way; it may well get ugly. Deal with it ;p

10
  • 5
    Just because the code is ugly, doesn't mean it has to be unmaintainable. Comments and indentation are free, and ugly code can usually be encapsulated into a manageable entity (class, module, package, function, depending on the language). The code may be equally ugly, but then at least people will be able to judge the impact of changes they are about to make to it.
    – tdammers
    Jul 5, 2011 at 7:14
  • 8
    @tdammers indeed, and I try to do so as far as possible; but it is a bit like putting lipstick on a pig. Jul 5, 2011 at 7:15
  • 2
    Well, maybe one should make a distinction between ugly syntax and ugly algorithms - ugly algorithms are sometimes necessary, but ugly syntax is generally inexcusable IMO.
    – tdammers
    Jul 5, 2011 at 7:17
  • 5
    @IMO ugly syntax is pretty unavoidable if what you are doing is by it's nature several levels of abstraction underneath the usual language level. Jul 5, 2011 at 7:23
  • 2
    @marc ... thats interesting. My first reaction to meta/abstract being ugly was suspicion about the specific language/plateform being non-conducive to meta coding rather than some underlying law connecting the two. The thing that made me believe that was the example of progressive metalevels in mathematics ending in set-theory whose expression is hardly ugleir than algebra or even concrete arithematic. But then set-notation is probably a different language altogether and every abstration level beneath has its own language....
    – explorest
    Jul 5, 2011 at 18:41
28

When you've profiled the code and verified that it is actually causing a significant slowdown.

10
  • 3
    And what is "significant"?
    – Rook
    Jul 5, 2011 at 2:10
  • 3
    @hotpaw2: it's the intelligent answer - it assumes the developers are at least somewhat competant. Otherwise yes, using something faster than bubble sort is (usually) a good idea. But too often someone will (to keep with sorting) swap quicksort for heapsort for a 1% difference, only to see someone else swap it back six months later for the same reason.
    – Мסž
    Jul 5, 2011 at 3:40
  • 1
    There is never a reason to make non-clean code. If you can't make your efficient code clean and easy-to-maintain you're doing something wrong. Jul 5, 2011 at 6:02
  • 2
    @SF. - the customer will always find it too slow, if it can be faster. He doesn't care for "cleanless" of the code.
    – Rook
    Jul 5, 2011 at 11:40
  • 1
    @Rook: Customer may find the (trivial) interface code too slow. Some pretty simple psychological tricks improve the user experience without actually accelerating the code - defer button events to a background routine instead of performing the actions on the fly, show a progressbar or something like that, ask some insignificant questions while the activity is performed in background... it's when these don't suffice, you may consider actual optimization.
    – SF.
    Jul 5, 2011 at 12:41
15

Clean code is not necessarily exclusive with fast-executing code. Normally difficult-to-read code was written because it was quicker to write, not because it executes any faster.

Writing "dirty" code in an attempt to make it faster is arguably unwise, since you don't know for certain that your changes actually improve anything. Knuth put it best:

"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified."

In other words, write the code clean first. Then, profile the resulting program and see if that segment is, in fact, a performance bottleneck. If so, optimize the section as necessary, and be sure to include plenty of documentation comments (possibly including the original code) to explain the optimizations. Then profile the result to verify that you actually made made an improvement.

0
11

Since the question says "fast hard to read code", the simple answer is never. There's never an excuse for writing code that's hard to read. Why? Two reasons.

  1. What happens if you're hit by a bus on your way home tonight? Or (more optimistically, and more typically) taken off this project and reassigned to something else? The small benefit you imagine you've made with your tangled mess of code is totally outweighed by the fact that nobody else can understand it. The risk this poses to software projects is hard to overstate. I worked once with a major PBX manufacturer (if you work in an office you probably have one of their phones on your desk). Their project manager told me one day that their core product - the proprietary software that turned a standard Linux box into a fully-feature phone exchange - was known inside the company as "the blob". Nobody understood it any more. Every time they implemented a new feature. they'd hit compile then stand back, close their eyes, count to twenty, then peek through their fingers to see if it worked. No business needs a core product they no longer control, but it's a frighteningly common scenario.
  2. But I need to optimise! OK, so you've followed all the excellent advice in other answers to this question: your code is failing its performance test cases, you've profiled it carefully, identified the bottlenecks, come up with a solution... and it's going to involve some bit-twiddling. Fine: now go ahead and optimise. But here's the secret (and you may want to sit down for this one): optimisation and reduction in source code size are not the same thing. Comments, white space, brackets and meaningful variable names are all huge aids to readability that cost you absolutely nothing because the compiler will throw them away. (Or if you're writing a non-compiled language like JavaScript - and yes, there are very valid reasons to optimise JavaScript - they can be dealt with by a compressor.) Long lines of cramped, minimalist code (like the one muntoo has posted here) have nothing to do with optimisation: that's a programmer trying to show how clever they are by packing as much code into as few characters as possible. That's not clever, it's stupid. A truly clever programmer is one who can communicate their ideas clearly to others.
1
  • 2
    I can't agree that the answer is "never". Some algorithms are inherently very difficult to understand and/or to implement efficiently. Reading the code, regardless of the number of comments, may be a very difficult process.
    – Rex Kerr
    Jul 6, 2011 at 22:43
5

When it is throw-away code. I mean that literally: when you write a script to perform a one-off calculation or task, and know with such certainty you will never have to do that action again that you can 'rm source-file' without hesitation, then you may choose the ugly route.

Otherwise it is a false dichotomy--if you think you need to make it ugly to do it faster, you're doing it wrong. (Or your principles about what is good code need revising. Using goto is in fact quite elegant when it is the proper solution to the problem. It rarely is however.)

5
  • 5
    There is no such thing as throw-away code. If I had a penny for every time "throw-away code" made it into production because "it's working, we don't have the time to rewrite it", I'd be a millionaire. Every line of code you write should be written such that another competent programmer can pick it up tomorrow after you're struck by lightning tonight. Otherwise don't write it. Jul 5, 2011 at 6:41
  • I do not agree it is a false dichotomy; IMO there are scenarios especially in library code (not so much in application code) where the split is very real. See my answer for more. Jul 5, 2011 at 6:41
  • @mark, if the "another competent programmer" is really competent, then the throw-away code should not be a problem either :)
    – user1249
    Jul 5, 2011 at 7:32
  • @Mark - Easy. Just write the throw-away code so that it will fail any production testing, perhaps in some unfixable manner.
    – hotpaw2
    Jul 5, 2011 at 12:28
  • @Mark, if your “throw-away code” makes it into production, then that is not throw-away code. Notice that I took the time in my answer to clarify that I am talking about code that is literally throw-away: i.e., delete after first use. Otherwise I agree with your sentiment and said as much in my answer.
    – maaku
    Jul 5, 2011 at 15:09
4

Don't forget you can make hard-to-read code easy to understand by appropriate documentation and commenting.

In general, profile after you've written easy-to-read code that does the desired function. Bottlenecks might require you to do something that makes it look more complicated, but you fix that by explaining yourself.

3

Whenever the estimated cost of lower performance in the marketplace is greater than the estimated cost of code maintenance for the code module in question.

People still do twisted hand-coded SSE/NEON/etc. assembly to try and beat some competitor's software on this year's popular CPU chip.

1
  • A good business perspective, sometimes programmers need to look beyond the merely technical.
    – this.josh
    Jul 5, 2011 at 6:40
0

To me it's a proportion of stability (as in cemented in concrete, clay baked in the oven, set in stone, written in permanent ink). The more unstable your code is, as in the higher the probability that you'll need to change it in the future, the more easily pliable it needs to be, like wet clay, to stay productive. I also emphasize pliability and not readability. To me the ease of changing code is more important than the ease of reading it. Code can be easy to read and a nightmare to change, and what use is being able to read and easily understand implementation details if they're a nightmare to change? Unless it's just an academic exercise, typically the point of being able to easily understand code in a production codebase is with the intent of being able to more easily change it as needed. If it's difficult to change, then a lot of the benefits of readability go out the window. Readability is only generally useful in the context of pliability, and pliability is only useful in the context of instability.

Naturally even the most difficult to maintain code imaginable, regardless of how easy or hard it is to read, doesn't pose a problem if there's never a reason to change it, only use it. And it is possible to achieve such a quality, especially for low-level system code where performance often tends to count the most. I have C code I still use regularly which hasn't changed since the late 80s. It hasn't needed to change since then. The code is fugly, written in the bit-fiddling days, and I barely understand it. Yet it's still applicable today, and I don't need to understand its implementation to get plenty of use out of it.

Thoroughly writing tests is one way to improve stability. Another is decoupling. If your code doesn't depend on anything else, then the only reason for it to change is if it, itself, needs to change. Sometimes a minor amount of code duplication can serve as a decoupling mechanism to dramatically improve stability in a way that makes it a worthy trade-off if, in exchange, you get code which is now completely independent of anything else. Now that code is invulnerable to changes to the outside world. Meanwhile code that depends on 10 different external libraries has 10 times the reason for it to change in the future.

Another helpful thing in practice is to separate your library from the unstable parts of your codebase, possibly even building it separately, as you might do for third party libraries (which likewise are meant to just be used, not changed, at least not by your team). Just that type of organization can prevent people from tampering with it.

Another is minimalism. The less your code tries to do, the more likely that it can do what it does well. Monolithic designs are almost permanently unstable, since the more and more functionality gets added to them, the more incomplete they seem.

Stability should be your primary goal whenever you aim to write code that's inevitably going to be difficult to change, like parallelized SIMD code which has been micro-tuned to death. You counteract the difficulty of maintaining the code by maximizing the likelihood that you won't have to change the code, and therefore won't have to maintain it in the future. That brings maintenance costs down to zero no matter how difficult the code is to maintain.

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