16

For the longest time in places like Java's IRC channel, SO, and other places I've been told something along the lines of "Worry about how the code looks and its readability/understandability now, and performance later if absolutely necessary". So for the longest time, I haven't really been OCD about performance for my small desktop or web apps, just removing the obviously inefficient.

Most responses are "What about scalability?". Thats a legitimate point, but if my app was only built to parse, say, files 10,000 lines long, should I make my code a mess for the small percentage of people that are going to shove in a 1,000,000 line file?

My main question is when should I trade the easy but somewhat inefficient ways of doing tasks for big giant complicated beasts that do things extremely quickly but destroy any possible ways of upgrading and make the code excessively difficult and prone to rewriting anyway by the next developer?

8 Answers 8

24

Worry about performance when it becomes a problem.

If you write a small app to process 10,000 line files and you get a 1,000,000 line file every 100th file, it probably doesn't matter that it takes longer to process that one file. However, if you are regularly getting files that are 5-10 times larger than initially and your application is taking too long to do its job, then you start profiling and optimizing.

Now, I said "too long to do its job". That is up to the user or sponsoring organization to decide. If I'm doing a task and it takes me 5 minutes to do something when it took me 3 without the software or with a different tool, I'd probably file a bug report or maintenance request to have that improved.

If you are the user, how long you want your software to take to do its job is up to you - only you can decide if you want it done faster or if you are willing to wait longer to have more readable code.

2
10

My main question is when should I trade the easy but somewhat inefficient ways of doing tasks for big giant complicated beasts that do things extremely quickly but destroy any possible ways of upgrading and make the code excessively difficult and prone to rewriting anyway by the next developer?

This is usually a false dichotomy. You can write wonderfully efficient, readable and maintainable code. You can write wonderfully inefficient, unmaintainable piles of mess.

When dealing with performance issues, I usually try to think about the business problem I am solving. How will my software behave when my customers use it. Will my applications performance make Jacob Nielsen happy?

3
  • 5
    ++ FALSE DICHOTOMY! Will they never learn? When you find and fix a performance problem, the code is not only quicker, it's better. I only regret that I have but one upvote to give! Sep 17, 2010 at 15:31
  • +1 for writing that it's USUALLY a false dichotomy... not always, but usually. May 25, 2011 at 12:53
  • 1
    -1 for writing it's usually a false dichotomy - fact is, it is usually correct, and only in rare cases a false dichotomy. In more than 30 years of my programming career I have seen too many "well intended" performance optimizations which in fact made the code harder to understand and maintain (and often optimized something which was totally unnecessary to be optimized).
    – Doc Brown
    Aug 28, 2012 at 6:15
5

A truism I picked up studying microprocessors in college that stayed with me: "Make the common case fast. Make the uncommon case correct."

As long as you have just a small percentage of users choking your code with input two orders of magnitude larger than what it was meant to handle, don't sweat it. Make sure it handles the input correctly if they give it long enough, and doesn't leave anything corrupted into uselessness if they kill the job before it finishes.

But, once more and more people start using it that way (or start telling you "You know, I'd dearly love to use that tool you wrote on my weekly TPS reports, but it takes all freakin' day"), that's when you start considering trading away ease of maintenance for performance gains.

1

My main question is when should I trade the easy but somewhat inefficient ways of doing tasks for big giant complicated beasts that do things extremely quickly but destroy any possible ways of upgrading and make the code excessively difficult and prone to rewriting anyway by the next developer?

"Worry about how the code looks and its readability/understandability now, and performance later if absolutely necessary" is the easy way out, and generally unhelpful. a good design will be easy to maintain, easy to read, and efficient.

performance is one common component of a good design. if your program is slow and wasteful, it's really not reusable. when you need to fix that mess, you force updates on your clients, unless it's just too time consuiming for them to update. that slow program becomes the big mess that is too costly to improve. then they choose an alternative becasue it does not suit their needs. diagnosing, updating, and dealing with side effects of improvements to a bad design often outweigh the initial development time of writing it to be efficient, work correctly, and has a genrally good design. that program is highly reusable and requires low maintentance (win).

so, the short answer to your question is "don't be wasteful. write for reuse. it's ok to be lazy when prototyping/developing proofs of concepts, but don't use that prototype for production code.".

do be aware of and avoid wasteful designs when writing production programs and programs that you intend to reuse. during implementation is the ideal time to write your program to not be wasteful - you have a clear idea of the details and its operation, and it's really painful and ineffective to fix after it's written. a lot of people believe a little profiling (maybe) at the end or if there is a problem is adequete, when it's usually too time consuming to redesign/change and the inefficiencies are so many and so widespread that you don't understand the program well based on the results of a profile. this approach takes little time during implementation and (assuming you have done this enough times) typically results in a design that is several times faster, and is reusable in many more contexts. not being wasteful, choosing good algorithms, giving thought to your implementations, and reusing the right implementations are all components of good design; all of which improves readability, maintainability, and reuse more often than hurts it.

0

I try to make code readable - performance be damned.

When, and if, code proves to be too slow, I will refactor it to be faster. Usually refactoring process is followed with lot of comments since code does tend to be less readable.

0

Um - Never?

Seriously, code should always be written so as to be easily understood and maintained.

With regard to when to deal with performance problems, deal with them once you identify them, don't pre-optimize your code because then you'll just be guessing about where the performance problems are.

If your code is written so as to be clear, concise, understandable, and maintainable then you or another programmer should have no problem refactoring the code to make it more efficient.

3
  • 3
    I don't agree with this. A performance requirement is a valid non-functional requirement for a system.
    – Thomas Owens
    Sep 1, 2010 at 21:31
  • Technically if there is a clearly defined performance related requirement then it could be said that you have identified a performance problem and have to account for it in your solution. What I'm talking about is getting clever in advance so that you can avoid non-specific 'potential' problems. Sep 1, 2010 at 21:45
  • Ah. Yeah, your absolutely right in that case. You don't worry about the possibilities because there are so many, but focus on what you know.
    – Thomas Owens
    Sep 2, 2010 at 13:51
0

I normally write code to be readable first and foremost. If, and only if, I find that the program runs too slow to do its job, do I profile and optimise. That said, there is nothing wrong with getting into the habit of performing common optimisations that don't affect the readability of your code. That is, if a piece of code can be written in two equally (or nearly equally) readable ways, choose the one that's usually faster.

For example, in Python, list comprehensions (or generator expressions) tend to be faster than the equivalent for loop, so I use list comprehensions where I can, if they don't affect readability (for instance, I don't nest list comprehensions if I can avoid it, and use a for loop instead because nested list comprehensions can be hard to mentally parse).

Similarly, immutable data types tend to be faster than mutable ones, so I use immutable data types where I can.

0

If you're working in genuinely performance-critical areas, then you can't put efficiency off as an afterthought. It's one of the most critical things to think about when designing early on in those cases and in ways that relate to the maintainability of the end result.

You can't design and implement a large-scale server and just start off writing easy, well-documented code that just uses blocking functions for everything with a global thread lock that locks the entire system to process each individual client request while not putting any thought whatsoever into shared state, thread contention, and asynchronicity. Such is a recipe for disaster and a need to redesign and rewrite the bulk of the nicely-documented code you wrote in ways that could lead to the most difficult-to-maintain codebase imaginable, plagued by race conditions and deadlocks as a result of trying to achieve the required efficiency in hindsight, as opposed to having just having thought about efficient, simple, working designs upfront.

A game development team 8 months into production with an engine that only goes 2 frames per second on their beefiest hardware with 32 cores while having a tendency to stall for 15 seconds every time the screen gets busy is unlikely to instantly get a usable product by just fixing one little localized hotspot. Chances are that their design is FUBAR in ways that warrant an epic revisiting of the drawing board and design changes which could cascade into every corner of the codebase.

With John Carmack, he talked once about how a tech demo has to run at the minimum of hundreds to thousands of frames per second in order to integrate it into production. That's not an unhealthy obsession with efficiency. He knows upfront that games need to run, in their entirety, at 30+ FPS for the customers to find it acceptable. As a result one little aspect like a soft shadow system can't be running at 30 FPS, or else the game as a whole can't possibly be fast enough to provide the required realtime feedback. It's unusable until it achieves the required efficiency. In such performance-critical areas where there's a fundamental requirement for efficiency, a solution that fails to achieve adequate speed is actually no better than one that doesn't work at all, since both are completely unusable. And you can't design an efficient soft shadow system that runs at hundreds to thousands of frames per second as required for a realtime game engine unless you put a predominant amount of thought upfront as to its efficiency. In fact, in such cases, 90+% of the work is oriented around efficiency since it's trivial to come up with a soft shadow system that works just fine at 2 hours per frame using path tracing, but you can't expect to tune it to running at hundreds of frames per second without a totally different change in approach.

When efficiency is a fundamental part of an application's design, you can't expect to achieve efficiency in hindsight without losing dramatically more time than you saved by ignoring it, since you can't expect to achieve a working design in hindsight. No one says ,"it's okay to put off thinking about design till later. Just document your code well and you can come up with a proper design later." But in performance-critical architectures, that's what you are effectively doing if you don't put a great deal of care and thought into efficient designs upfront.

Now that doesn't mean you have to micro-tune your implementations right off the bat. For implementation details, there's a lot of room to iterate towards faster solutions after measuring provided that the design won't need to change, and often that's the most productive way to go about it. But at the design level, it does mean you have to put the sufficient thought into how the design and architecture will relate to efficiency right from the start.

The key difference here is design. It's not easy to make big changes to designs in hindsight as designs accumulate dependencies, and the dependencies will break if the design changes. And if a design has a requirement to be reasonably efficient or, in some cases, that its quality is largely measured by its efficiency, then you shouldn't expect to be able to achieve a proper design as an afterthought. With any competitive products where efficiency is a huge aspect of quality whether it's operating systems or compilers or video processors or raytracers or game engines or physics engines, thoughts about efficiency and data representations were meticulously thought about from the very beginning. And in those cases it's not premature optimization to put so much thought into efficiency upfront. It was placing such thought exactly at the most productive time to do it, and right from the beginning.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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