DEV Community

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

Posted on • Updated on • Originally published at sendilkumarn.com

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

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"));
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

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()));
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
    }
Enter fullscreen mode Exit fullscreen mode

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" ;
Enter fullscreen mode Exit fullscreen mode

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";
....
Enter fullscreen mode Exit fullscreen mode

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

Latest comments (17)

Collapse
 
ujaehrig profile image
Ulf JΓ€hrig

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.

Collapse
 
elmuerte profile image
Michiel Hendriks

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"))
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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.

Collapse
 
sendilkumarn profile image
Sendil Kumar

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.

Collapse
 
sendilkumarn profile image
Sendil Kumar

Thanks for the Optimizing Java book

Collapse
 
ssimontis profile image
Scott Simontis

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.

Collapse
 
sendilkumarn profile image
Sendil Kumar

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.

Collapse
 
technophile profile image
Vatsal Hirpara

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

Collapse
 
mt3o profile image
mt3o

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.

Collapse
 
awwsmm profile image
Andrew (he/him)

Especially things like #6. I'm sure the JVM would inline those calculations if the intermediate variables aren't used anywhere else.

Collapse
 
sendilkumarn profile image
Sendil Kumar • Edited

The #6 is just an example.

I completely agree JVM optimizes in a lot of ways.

Thread Thread
 
sendilkumarn profile image
Sendil Kumar • Edited

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.

Thread Thread
 
mt3o profile image
mt3o

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

Collapse
 
hussein_cheayto profile image
hussein cheayto

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.

Collapse
 
bsangamesh profile image
B.SANAGPPA

Hi @hussein cheayto, can you please tell the author name or mention Amazon link for the book "clean code". Thank you.

Collapse
 
kuldeepsidhu88 profile image
Kuldeep Singh

Here is the amazon link - amzn.to/2Y3wpb6

Collapse
 
sendilkumarn profile image
Sendil Kumar

I couldnt agree more πŸ‘

Some comments may only be visible to logged-in visitors. Sign in to view all comments.