There is a famous saying credited to Donald Knuth that many programmers take at face value and use as an excuse to ignore the serious topic of software performance. Performance considerations should not affect software design adversely, and unrolling every loop you write is not the way to go, but that’s not what I’m talking about. There is a simple set of common sense practices that every engineer is responsible for knowing and following, regardless of his position in the workplace. Programming is a science and computers are the powerful medium, as much as hydrogen is to the chemist. Here is a number of performance ground rules to help prevent the software equivalent of smoking near a gas valve.
1. Understand and respect the basic principles of the hardware you’re writing for
Software engineers need a thorough knowledge and reverence for their medium just as a carpenter does wood and a blacksmith metal. In general for consumer devices processing throughput will hit a wall before you run out of memory. Computers are good at doing the same thing in large batches with minimal changes to state (rendering polygons, multiplying numbers, testing planar intersections, etc.). Integers and floating point calculations are both possible, but mixing them willy-nilly is almost always a bad idea. Data with good locality is fast (just like with the materials on your desk), whereas going out to memory and far flung parts of it frequently (getting up from your seat every time you need to check a book on the shelf), really slows things down.
Some good habits to get into:
- Standardize data types across subsystems by default (float, int, quaternions, unicode strings), but make exceptions for exceptional reasons
- When memory is abundant, cache intermediate and frequently referenced state
- Layout and group data together in memory when the size and access is going to be significant
- Do loop processing in an order that keeps the same pages of memory in the cache (swapping the X and Y order for iterating over a 2D region represented by a 1D array can make a big difference)
- Use the constructs built into the language and libraries you have, don’t ignore them (C++ has tons these, and they get better with every revision)
2. Processing data is the same as any other labor, don’t foster bureaucracy out of laziness
A function returns a current position is guaranteed to be valid if internally it computes the derived result every time, but if it could only change once every update of the main loop, why calculate it every time it’s referenced? Building block functions that return a group of objects based on a key are useful, but if they involve iterating over some large set in the process, they’re not the the kind of code that should be the backbone for processing one of many instances. This sounds obvious, but it’s not uncommon to find a junior programmer searching through a huge list inside of a doubly-nested loop, producing the same result tens of thousands of times. This goes for all function sizes and the more commonly used the more important. Constructors are functions too! Throwing around complex types passed by value in C++ can incur hundreds of unseen copy operations, occurring with parameters as well as return values. Be aware of what each statement is doing under the hood, this is the difference between a professional and a hack.
3. Use the right tools for the job
Would you use a wrench to drive in a nail just because you’d been working with it all week and the hammer was upstairs? If you have several languages at your disposal, choose the right one for the task at hand. Script languages are great for experimentation and prototyping because of their iteration speed, but don’t write entire UI frameworks that perform thousands of calculations every cycle in them. And don’t even think of writing another container class or max/min function when there are numerous industry-proven implementations available.
4. Take responsibility for your actions
Most importantly, be aware of the performance issues at hand, and have the professionalism to be honest about the ramifications of your code. If you need to use inefficient means to implement some critical feature for the time being, put in a comment that you’re aware of it and it needs to be revisited soon, better yet enter a quick item in your task management system and set a due date. Finally, when it is your code that is found to be inducing a three second blackout between menus, humbly do the repair and acknowledge that you will endeavor to avoid a repeat blunder.
Again, all this should be second nature to any journeyman developer, but unfortunately weak leadership can lead to a lot of bad habits and puzzled stares when these sort of topics come up about four days before a milestone (which sadly they often do). If you’ve been hired as a software engineer, whether you find it sexy or not, it’s your obligation to maintain awareness and diligence with your craft.
Writing performant software doesn’t have to be a separate task. When using best practices becomes the instinctual way you write, it takes no longer than sloppy code and saves you the effort of coming back to it later.