loading...
Cover image for 9 tips to Increase your Java performance β˜•οΈ πŸš€ πŸšΆβ€β™‚οΈ

9 tips to Increase your Java performance β˜•οΈ πŸš€ πŸšΆβ€β™‚οΈ

sendilkumarn profile image Sendil Kumar N Updated on ・4 min read

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 isoperations/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 the addAll 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. ❀️

Posted on by:

sendilkumarn profile

Sendil Kumar N

@sendilkumarn

An explorer wandering in the land of programs. I am passionate about Open Source. "Docendo discimus"

Discussion

pic
Editor guide
 

This is not good advice.

First of all, it breaks rule #1 and #2 of optimization.

Rule 1: Don't do it.
Rule 2 (for experts only). Don't do it yet - that is, not until you have a perfectly clear and unoptimized solution.
-- Michael A. Jackson

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, and n is usually small.

Applying these "performance improvements" to your code will have little to no impact.

Nobody is going to write

new HashSet<>(new ArrayList<>("one", "two", "three"))

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.

new HashSet<>(list.size()).addAll(list);

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:

  1. JVM optimizes "hotspots". Small methods become "hot" more quickly.
  2. JVM is better at (re)optimizing small methods.
  3. Huge methods are disqualified for optimization.

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.

 

Applying these "performance improvements" to your code will have little to no impact.

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.

Performance improvement suggestions should be accompanied with why one performs better than the other.

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))

new HashSet<>(list.size()).addAll(list);

I would be surprised, if they are not almost identical.

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.

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 book

 

Some 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.

Benchmark                     Mode  Cnt      Score     Error  Units
Benchmark.First.stringBuffer  thrpt   25  52791,073 Β± 196,355  ops/s
Benchmark.First.stringConcat  thrpt   25   7355,182 Β±  39,284  ops/s

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...

 

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.

 

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.

 

Some of these promote speed over legibility

That is a vague statement. Have you read the first quote? I think that clearly specifies clean code is important.

I try to make intentions clear when writing code, even if it isn't the fastest.

This was exactly the point.

the engines were designed to score well on benchmark tests that have little to do with real-world usage of the engine.

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.

 

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.

 
 

Thanks for the article. Definitely gonna use all these in solving competitive programming questions.