Entry 15, Day 51
As promised, today’s episode is going to be about low-level optimization. First, a quote.
„Premature optimization is the root of all evil”
– Donald Knuth
Yes indeed. We all love when things go faster than slower, but deadlines are tight and working, stable software with acceptable performance is better than software which is fast but unstable or not working at all. There is no point in tuning your Java computation to take 5 milliseconds instead of 50, when you are going to wait for database query that takes 5 seconds (optimize the query, maybe?). Also, in case of web application, there will be network latency of tenths or hundreds of milliseconds. Another issue is code readability. Sometime, to run faster, the code has to be more complicated and that’s always pain in the ass…
So when to optimize?
First, get to know your API. There are many small problems, that are already solved by standard Java library. And solved well, for example using native OS code, which will be more efficient. Also, instead of solving a problem, you just use a ready solution, which also more concise. Win – win situation.
Second, investigate, observe how the code is run. How often, and with what code. And by whom. Use your judgment to decide whether it is worth to invest in speed at this particular case. Ask your colleagues. Ask your Product Owner (You don’t have a Product Owner? Maybe start with that particular problem…). Ask users, browse your bug tracking system. If something becomes performance problem, usually someone will be bitching about that.
Third, there are cases where the code was good enough back in the old days of your company, where you had 1000 users. But suppose that now you have 10 millions and there is some kind of fancy algorithm with n^2 complexity that computes something on all users. Well, perhaps it’s time to take a second look at it.
Fourth, there is an issue of scale. If your system has 10 000 users, performance might not be that important. But if it has 1.29 billion (like one of social network sites, you may be familiar with), taking 2 millisecond out of each request processing will probably save hundreds of hours of computing time per day, Time means electricity and hardware. Which means money. Cutting application boot time will also save you a lot of money if you have that kind of scale (lots of servers). Surprisingly, boot time problems may originate in some inefficient Java code, e.g poorly constructing some kind of custom cache.
Let’s move to some examples.
String vs StringBuilder
Absolute classic. If you concatenate a lot, use StringBuilder append instead of String append or += operator. If you go into tens of thousands of concatenation, there will be tremendous difference in performance (three to four orders of magnitude).
If you need to fill each array cell with given value, use Arrays.fill instead of doing it manually in a loop. It will be faster two to three times, which does not make a lot of difference anyway being a fast operation, but at least looks better.
There are at least four ways to copy an array in Java:
- Manual for loop
- Object’s clone
In case of several millions of elements, the fourth will be roughly five times faster than first. Still it’s fast so just do it for clean code sake.
If you have a List, never, ever iterate over it using an index. If you happen to get LinkedList, you are screwed, because linked list traversal that way is a n^2 operation. When I was doing performance tests three years ago on some crappy core 2 duo laptop, traversing LinkedList with 1 million elements that way took 6 minutes. On the other hand using foreach loop took mere 25 milliseconds. Do the math. If you need an index badly, just use an external variable for that. What if you have created the list yourself and you know that it’s ArrayList? Well someone else may change that in the future without changing how the list is iterated. Be defensive.
ArrayList vs LinkedList
Which list to use? ArrayList is almost always a better choice. Unless you are adding and removing a lot of elements using iterator, use ArrayList. LinkedList has a linear cost for retrieving, adding and removing elements using index, while for ArrayList get is constant and add is constant amortized (for resizing the array from time to time). Also LinkedList takes much more memory per node (I will talk about details later).
When checking if a string matches some regular expression you may use the String’s matches method. However it will compile the given expression each time, so if you are doing this in a huge loop with complex expression there is a better idea: Pattern and Matcher. Create the Pattern once, and then create Matchers from that Patern for each string. For short strings and complex patterns, this may save you well over one order of magnitude of execution time.
Parsing a numeric string is one order of magnitude slower than producing string from numeric value. Which in turn is two orders of magnitude slower than mathematical operations on numeric primitives. If you have a massive amount of data, unnecessary converting the representation between texts and numbers will cost you a lot.
Primitives vs Wrappers
Operations on primitives are generally faster than on wrappers, since vm has to perform boxing and unboxing. It may take half of the entire math time, but still is fast. On the other hand, primitives has no null value, and usually you need to represent lack of value in some way. Bigger issue is actually size of wrappers vs size of primitives, but this is the topic for the next episode.
We have talked about some speed issues, your code will now run darn quick (just because the sole fact that you read my blog, remember!). Now let’s check out if your objects out there aren’t fatty little bastards, and what we can do about that. You think that 16GB of ram is a lot? Yeah, a lot do. See you in a few days.
February 26, 2017 at 5:49 pm
“…so if you are doing this in a huge loop with complex expression there is a better idea: Patern and Matcher.”
“…and then create Matchers from that Patern for each string.”
I guess it should be Pattern (are you referring to java.util.regex.Pattern) instead of Patern?
February 26, 2017 at 6:26 pm
Yes, that was a typo, thanks!