Any fool can write code that a computer can understand. Good programmers write code that humans can understand. - Martin Fowler
But the itch to write a high performance code is always there for any developer. Let us see how to make Java code to run even faster.
Note: The JVM optimizes the code efficiently. So you do not need to optimize it for a general use cases. But if you want to drain the maximum performance out of JVM. Here we go.
All the tests are taken in OpenJDK 12.0.1 in a Macbook Pro 2017 laptop.
1. Instantiate in constructor
If your collections are initialized only once, it is better to initialize the values in the collection constructor
itself rather than instantiating the collections and setting the values using addAll
.
// Slower πΆββοΈ
Set<String> set = new HashSet<>();
set.addAll(Arrays.asList("one", "two", "three"));
// Faster π
Set<String> set = new HashSet<>(Arrays.asList("one", "two", "three"));
Let us verify this using JMH benchmarks.
The results unit is
operations/second
(op/s
). More the number higher the performance is.
@State(Scope.Thread)
public static class MyState {
@Setup(Level.Trial)
public void doSetup() {
var arr = new Integer[100000];
for (var i = 0; i < 100000; i++) {
arr[i] = i;
}
list = Arrays.asList(arr);
}
public List list;
}
// Faster π > ~148,344 op/s
@Benchmark
public HashSet usingConstructor() {
var set = new HashSet<>(list);
return set;
}
// Slower πΆββοΈ > ~112,061 op/s
@Benchmark
public HashSet usingAddAll() {
var set = new HashSet<>();
set.addAll(list);
return set;
}
The
construtor
version provides ~36000 op/s more than theaddAll
version.
2. AddAll is faster than Add
Similarly, addAll
provides higher operations per second when compared with add
. So next time when you are adding something to an array make sure that you pile them and add it using addAll
.
// Slower πΆββοΈ ~116116op/s
@Benchmark
public ArrayList<Integer> usingAdd() {
var a = new int[1000];
for (var i = 0; i < 1000; i++) {
a[i] = i;
}
var arr = new ArrayList<Integer>();
for (var i = 0; i < 1000; i++) {
arr.add(a[i]);
}
return arr;
}
// Faster π ~299130 op/s
@Benchmark
public ArrayList<Integer> usingAddAll() {
var a = new Integer[1000];
for (var i = 0; i < 1000; i++) {
a[i] = i;
}
var arr = new ArrayList<Integer>();
arr.addAll(Arrays.asList(a));
return arr;
}
The addAll
is almost twice as fast as the add
version.
3. Use EntrySet
for Map over KeySet
Do you iterate a lot over the map? Then use entrySet
over the keySet
.
// Slower πΆββοΈ ~37000 op/s
@Benchmark
public HashMap<Integer, Integer> keySetIteration(Blackhole blackhole) {
var someMap = new HashMap<Integer, Integer>();
for (var i = 0; i < 1000; i++) {
someMap.put(i, i);
}
var sum = 0;
for(Integer i: someMap.keySet()) {
sum += i;
sum += someMap.get(i);
}
blackhole.consume(sum);
return someMap;
}
// Faster π ~45000 op/s
@Benchmark
public HashMap<Integer, Integer> entrySetIteration(Blackhole blackhole) {
var someMap = new HashMap<Integer, Integer>();
for (var i = 0; i < 1000; i++) {
someMap.put(i, i);
}
var sum = 0;
for(Map.Entry<Integer, Integer> e: someMap.entrySet()) {
sum += e.getKey();
sum += e.getValue();
}
blackhole.consume(sum);
return someMap;
}
The entrySet
can run 9000
operations more than its keySet
variant in a second.
4. Use SingletonList
instead of an array
with single element.
// Faster π
var list = Collections.singletonList("S");
// Slower πΆββοΈ
var list = new ArrayList(Arrays.asList("S"));
5. Use EnumSet
instead of HashSet
. EnumSet
is much faster.
// Faster π
public enum Color {
RED, YELLOW, GREEN
}
var colors = EnumSet.allOf(Color.class);
// Slower πΆββοΈ
var colors = new HashSet<>(Arrays.asList(Color.values()));
More about EnumSet here.
6. Do not initialize objects at will. Try to reuse at the maximum.
// Faster π
var i = 0 ;
i += addSomeNumber();
i -= minusSomeNumber();
return i;
// Slower πΆββοΈ
var i = 0 ;
var j = addSomeNumber();
var k = minusSomeNumber();
var l = i + j - k;
return l;
7. Use String.isEmpty()
method to check whether the String
is empty.
String is a byte[]
and isEmpty
just checks the length of an Array
. So it is much faster.
public boolean isEmpty() {
return value.length == 0;
}
8. If you are using String with a single character replace them with a Character
// Faster π
var r = 'R' ;
var g = 'G' ;
var b = 'B' ;
// Slower πΆββοΈ
var r = "R" ;
var g = "G" ;
var b = "B" ;
9. Use StringBuilder wherever you can.
// Faster π
StringBuilder str = new StringBuilder();
str.append("A");
str.append("B");
str.append("C");
str.append("D");
str.append("E");
....
// Slower πΆββοΈ
var str = "";
str += "A";
str += "B";
str += "C";
str += "D";
str += "E";
....
But when you have to do a single concatenation. instead of using a StringBuilder it is faster to use
+
.
If you have an interesting performance tip / something is wrong, please leave that in the comment.
You can follow me on Twitter.
If you like this article, please leave a like or a comment. β€οΈ
Top comments (17)
This is not good advice.
First of all, it breaks rule #1 and #2 of optimization.
Second of all, most of these optimizations are inconsequential. Sure you can show with a benchmark that one is faster than the other. That is because you are making
n
big by benchmarking, andn
is usually small.Applying these "performance improvements" to your code will have little to no impact.
Nobody is going to write
That's what
Arrays.asList()
does.It would be better to explain why one is faster than the other. Per your advice, the following code would be much slower, right? Try it, you'll be surprised.
Performance improvement suggestions should be accompanied with why one performs better than the other. In this case, it has everything to do with time spend in resizing the destination array of the
HashSet
. If you make it large enough to begin with, no resizing was needed. This is what happens with the constructor call.If you really want to read up on Java performance improvements I can suggest the book Optimizing Java. It is a new book focused on newer Java versions, 6 and up to 12. Do note that this book is for an more advanced audience.
I can give you one simple Java performance optimization trick: Write small methods. There are a few reason for this:
So if you refactor your large method to 3 smaller methods, and 1 medium method. Then your 3 small methods might be optimized at some point, and make your code faster than.
So if you write Clean Code you not only make your code easier to understand, it can also grant you a performance boost.
Again and yet again, I am reiterating here
Note: The JVM optimizes the code efficiently. So you do not need to optimize it for a general use cases. But if you want to drain the maximum performance out of JVM. Here we go.
Yeah we can explain each one of them. Show the difference in the byte code and how JVM optimizes them and things like that (Maybe in future (when I have time))
I would be surprised, if they are not almost identical.
Create
smaller
methods, of course is in every clean code / perf optimization book.But look out this when running on smaller devices, sometimes too many smaller methods will result in more inlining that will increase the memory pressure.
Thanks for the
Optimizing Java
bookSome of the things are irrelevant because recent JVM can optimize them. I'm taking about StringBuffer and perhaps some stuff involving inlining of your code. However the 1000 repetition limit is not enough to experience that.
Especially things like #6. I'm sure the JVM would inline those calculations if the intermediate variables aren't used anywhere else.
The #6 is just an example.
I completely agree JVM optimizes in a lot of ways.
StringBuffer is way more efficient than normal String concatenation.
And this is on JDK 12.
For some reason your JDK decided out to replace str concat with stringbuilder.
Look here for more information:
dzone.com/articles/jdk-9jep-280-st...
Some of these promote speed over legibility, which I think is a dangerous mindset. I try to make intentions clear when writing code, even if it isn't the fastest. If it appears to be an issue, I can then profile the code and research effective changes. Without realistic use cases, I don't think benchmarks are of any value...that's part of why we have so many JS performance issues today; the engines were designed to score well on benchmark tests that have little to do with real-world usage of the engine.
That is a vague statement. Have you read the first quote? I think that clearly specifies clean code is important.
This was exactly the point.
The problem with JS performance are not due to engine. It is often because we do not know how this engine works. Irrespective of engine, if you are writing poly/mega-morphic code the performance will be slow. We have to understand that when writing. The engines are tuned for real life cases too but it is not possible to cover them fully.
The "use StringBuilder" is much too general. Maybe have a look at the following talk by Heinz Kabutz:
youtu.be/z3yu1kjtcok
He talks an hours about Java Strings and performance.
For performance surprises, the following talk is also a recommendation: youtu.be/fN3MtD-lNHc
Its conclusion, which I support, is: Performance is full of surprises, write clean code. If necessary focus on the hot spots.
Great article. Thanks for sharing :)
I would've added as a bonus point the book:"Clean Code".
It helped me to learn best practices and enhance my code.
I couldnt agree more π
Hi @hussein cheayto, can you please tell the author name or mention Amazon link for the book "clean code". Thank you.
Here is the amazon link - amzn.to/2Y3wpb6
Thanks for the article. Definitely gonna use all these in solving competitive programming questions.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.