The truth about Preventative Optimizations
Drop the “premature.” It’s cleaner.
After a lot of sweat and tears, Season 3 of Android Performance Patterns has launched bringing 12 new videos… alongside creating some grumpy engineers.
Let me explain : The majority of topics this season focused on discussing uncommon memory problems in your app. Stuff that you take for granted, or never really find because you’re not looking in the right places:
- Trimming and sharing memory talked about how your app gets killed when the system needs its memory back (and how horrible that is for you).
- The ArrayMaps video warns about the memory overhead of HashMaps, and how to avoid it.
- The AutoBoxing & SparseArrays videos talk about autoboxing overhead, and how to avoid it.
- The View Leaks content talks about one of the scariest memory leaks I’ve ever found.
- And finally, I suggest that you avoid using Enums in your java code.
When I first presented some of this content at Google IO, I immediately got an outcry from a handful of engineers who condemned me for preaching “premature optimizations.” In fact, one person on the show floor actually told me that I was being “reckless” for telling people to change things that “didn’t actually impact your app.”
Which, let’s be honest, couldn’t be farther from the truth.
It’s not “premature” it’s “preventative”
Here’s some things we know about memory on Android:
- Memory continues to be one of the most common performance issues for end-users on the platform.
- Memory is constantly shared between foreground and background apps. One rogue app can cause perf problems for the rest of the apps.
- The size of your DEX files influences application performance, as code blocks spend more time being paged in-and-out of caches.
Memory on Android is a scarce resource. You need to conserve it wherever you can. So is something like avoiding Enums in your code a “premature optimization?” Far from it.
Consider your daily oral hygiene routine. You KNOW that you’re going to eat food and it’s going to decay your teeth. So each day you brush them so that you prevent these problems. It’s a preventative measure, because you know that if you don’t the pain of pulling a tooth later really sucks. So you slog through the process of brushing your teeth everyday to avoid that huge pending problem.
Same thing goes for your code. You know that memory will hurt your performance on Android. Doing the little things like using the smaller-memory container, avoiding Enums, and using the right bitmap sizes are all “Preventative Optimizations.” It’s doing the little things, day in-and day-out to make sure that you won’t have to pull a (metaphorical) tooth later.
What Knuth really thinks about your code.
When talking about these types of code changes, many programmers like to wave them off as micro optimizations. Things not worthy of their time because the impact is so small compared to lower hanging fruit. Often regurgitating Donald Knuth’s famous words, which have been horribly taken out of context :
”We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.”
This statement is often quoted by engineers in an attempt to de-emphasize code performance in favor of other factors which they consider more important. Like rapid development, clarity, readability, risks of accidental bugs, and elegance.
As Josh Barczak points out, there’s also another great quote that Knuth said, which engineers forget :
“The conventional wisdom shared by many of today’s software engineers calls for ignoring efficiency in the small; but I believe this is simply an overreaction to the abuses they see being practiced by penny-wise- and-pound-foolish programmers, who can’t debug or maintain their ‘optimized’ programs.”
Oh snap! Once you dig deeper, it becomes very apparent that Knuth is a very pragmatic engineer. Optimizations matter when it’s time to build solid code. They don’t matter when it’s time to quickly prototype things. Even from Knuth’s own mouth : There is no single one-and-done rule. It’s all about the context, need, and design that dictates how much time should be spent optimizing or not.
The reality of the situation is that highly-optimized code can look nothing like what it actually does (especially true for vector-optimized SIMD code). Which does a great job at removing some elasticity from your code base. Things get harder to change because it’s difficult to understand the performance implications of such an action. It also goes so far as to obfuscate understanding from even the author of the code themselves. And where there’s lack of understanding, there’s certainly bugs. But let’s be honest, that’s a symptom of software engineering, not performance optimization.
As such, bike-shedding performance improvements in the name of code maintenance & readability is just as problematic; what good is your code if your app is running slow?
Go big and small.
So let’s get back to Android and memory. We know memory is a problem, and we know you’re trying to build a great, stable application for millions of users; if you’re not diligent, memory problems can keep you from that goal.
As we’ve already discussed on the APP video list, there’s a lot of big memory problems out there that you should avoid. Things like using the right Bitmap sizes and formats. Reducing object allocation churn. Re-using memory space with Object Pools and shared Bitmaps. Reducing network traffic with better algorithms. By all means, yes, keep doing those things 100%.
But don’t neglect the small stuff. The small preventative work that you do in your code to save the little bits memory from every corner of your application, which saves you big in the long run.
It’s not premature. It’s preventative.