The way users experience software is very subjective, of course, but there are some ultimate truths that hold for all users. One of these is that the speed of the interface is critical. Users want instant feedback and instant gratification. It is our job as designers to structure the interactions of users with our software in such a way that doesn’t feel sluggish or unresponsive. This is the case even if under the covers a huge amount of work takes place.
The Importance of Speed for the User Experience
In today’s world of infinite choice and overwhelming information user’s attention is a scarce resource. Many studies show for web sites hundreds of a second are significant and if a page doesn’t load fast enough they users will simply click away and disappear.
An interesting fact is that users don’t care about performance per se, but rather they care about perceived performance. It means that even if the task at hand takes longer than the user’s threshold, if they think that your application is working fast they’ll be satisfied. In the rest of this article I’ll explore different ways to take advantage of this aspect of user behavior
How to Measure Speed
Before making any optimizations you need to be able to measure the speed of whatever it is you’re trying to make faster. I can write books on this topic, but the first thing is to have a good profiling framework in place and identify hot spots that impact overall performance. For example, if you made your core algorithm 500 times faster, you may be a hero, but you also might have actually hurt overall system performance because your algorithm was maybe responsible for only 5% of the total time to process a request, however the new 500X algorithm is using more memory, trashes caches and causes the other components responsible for 95% percent of the time to be much slower.
This is the best strategy period! Not only will your code go faster, you will save memory, have fewer bugs and have more time to improve existing code. Note that taking out features might not yield the same benefits. My recommendation is to focus on new requests coming your way to add functionality that don’t sit well with the original vision. That doesn’t mean saying no to everything, just exercise that brain of yours and adopt a skeptical stance when appropriate.
This is not a great revelation. Everybody knows about progress bars or other progress indicators. But, there are a few nuances that are important. Some applications show only “I’m working” indicators. In the olden days it was the Windows hourglass and nowadays it is often the Mac’s spinning beach ball. This is not progress! It actually often symbolizes that the application is stuck and the only thing left working is your GUI happily informing you that it is working just fine. For many long running tasks there is no way to tell how long it is going to take. If you copy files from a remote server, then the load on the remote machine and the network condition will determine that. But, you can show progress by amount of work completed, which is even more relevant. Show something like 5/8 files copied. Even if some files are small and same are large and the progress is not uniform, your users will appreciate it. If you design APIs that perform long running tasks, provide a way to get progress information???either by allowing multiple calls to get status, through a separate progress API or through callbacks.
My all-time favorite technique. Cheat, Lie and provide statistics. 4774 studies and 83% of the world’s most renowned statistics experts agree that invented numbers are just as useful as real numbers. How can you cheat? Consider an email client with an indicator for new messages. To be honest every time a user gets a new message you have to update the counter that can involve a lot of chatty communication between client and server to check if there are new messages or not. 99% of the time the user may not even care. For the 1% who do care, is it really that important to tell that they have exactly 5, 6 or 7 new messages? The important thing is to distinguish between no new messages at all, some messages and a lot of messages. When it gets to the tons of messages level (for example, more than 100) you can quite safely assume the user is not that interested in responding immediately to any message on this account. May applications in this case just display 99+ and don’t update at all anymore. When the user actually goes to read messages, then all the messages can be downloaded and the correct number is shown.
The best performance cheat ever was in the game Ratchet and Clunk on the PlayStation. Whenever the player finished a level all the assets for the next level needed to be loaded from the DVD to memory. That was a long process that caused a significant pause in the game flow. The programmers cheated blatantly by making the main character stumble and fall before the finish line giving them some extra time to load the next level.
Being lazy means doing only what you have to do. There are many benefits for this approach. Your startup time is faster because you don’t do a lot of stuff upfront. The same goes for memory (you don’t load what you don’t need). It can make a huge difference between a sluggish app that takes forever to load and a snappy and responsive one.
Prefetching is exactly that opposite of being lazy. Prefetching is all about preempting the user and loading stuff ahead of time, so when the user needs something it is already there. It is also very useful for intermittently connected applications (either by design or by network conditions). By prefetching what you need, you are not surprised when the network suddenly goes away. How do you reconcile prefetching with being lazy? Both have their place (sometimes within the same application).
Break it up
Breaking up a long operation to multiple parts or even going all the way to streaming is another great tactic for some applications. If the user is requesting a gazillion items from a database she will be happy to get the first page quickly and the rest later.
These days multi-core, multi-processes and the cloud are the lay of the land. By parallelizing your work load you can achieve immense speedups. Note, that it is often pretty difficult to parallelize workloads that are by nature embarrassingly parallel to begin with.
Throw more hardware at it
Easiest solution ever, but not the cheapest and will often take you only so far. Jump the memory or bump the CPU and watch your performance numbers soar. In a distributed system, make sure you can load balance properly and just add more machines (or stronger machines) behind the load balancer.
Premature Optimization Is the Root of All Evil
Finally, the most important advice of all???are you sure you really need to go faster? In general, many techniques for improving performance hurt other important aspects of the system, such as flexibility, readability and maintainability. Don’t waste your time optimizing a system that’s fast enough and end up making it worse.