Micro optimizations in Java. Good, nice and slow Enum

Dmytro Dumanskiy
Javarevisited
Published in
4 min readAug 20, 2020

Enums are crucial part of every application larger than “Hello World“. We use them everywhere. They are actually very useful: they restrict the input, allow you to compare the values by the reference, provide compile-time checks, and make the code easier to read. However, using Enums has a few performance downsides. And we’ll review them in this post.

(Please consider all the code below from the point of performance)

(Please do not focus on numbers, they are just examples to prove the point)

Enum.valueOf

In Blynk, one of the most popular features is called “Widget Property”. It’s when you can change the appearance of the visual widget in real-time by sending from the hardware the command like that:

Blynk.setProperty(v1, “LABEL”, “My New Label”);

Let’s take a look at existing Enum from our server:

enum WidgetProperty {
LABEL,
COLOR,
ON_LABEL,
OFF_LABEL,
MIN,
MAX
}

And now look at this basic code:

String inWidgetProperty;
...
WidgetProperty property = WidgetProperty.valueOf(inWidgetProperty)

Do you see what is wrong here?

Let’s rewrite the above code and create our own method based on the switch statement:

This method isn’t precisely the same, as it doesn’t throw the IllegalArgumentException in case of an unknown value. However, it’s done on purpose. Because, as you know, throwing and creating the exception (because of the stack trace) is an expensive operation.

Let’s make a benchmark:

Results (lower score means faster):

Hm… Our custom method with the switch statement is two times faster than Enum.valueOf. Something is wrong here. We need to go deeper.

As you probably know, enum classes don’t have the Java valueOf() method. The compiler generates it during the compilation. So we need to look into the byte code of the generated class to understand what is going on there:

 LDC Lstring/WidgetProperty;.class
ALOAD 0
INVOKESTATIC java/lang/Enum.valueOf (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
CHECKCAST string/WidgetProperty
ARETURN

Looks like java.lang.Enum.valueOf method is invoked inside WidgetPtoperty.valueOf method. Fortunately, this method is present in the Java code. Let’s check it:

And enumConstantDirectory:

Aha, so all we have here is a regular volatile HashMap with the enum names as the keys. And it looks like in that particular case the switch statement just outperforms the HashMap.get() method.

Let’s add valueOf with own HashMap implementation to the test as well:

private final static Map<String, WidgetProperty> cache;
static {
cache = new HashMap<>();
for (WidgetProperty widgetProperty : values()) {
cache.put(widgetProperty.name(), widgetProperty);
}
}
public static WidgetProperty enumMap(String value) {
return cache.get(value);
}

Results (lower score means faster, enumMap results attached to the prev test):

Own HashMap implementation seems faster than regular Enum.valueOf() but still slower than the switch statement.

Let’s rewrite the test to have the random input:

Results (lower score means faster):

Interesting… Now, with the random input, the switch statement is 2 times slower. Looks like the difference is due to the branch prediction according to the Andrei Pangin comment. And the fastest option would be our own Map cache implementation (as it does fewer checks comparing to the Enum.valueOf).

Enum.values()

Now, let’s consider another example. It’s not so popular as the above one, but still present in many projects:

for (WidgetProperty property : WidgetProperty.values()) {
...
}

Do you see what is wrong here?

Again, Enum.values() method is generated during the compilation, so to understand what is wrong there we have to look at the byte code one more time:

 GETSTATIC string/WidgetProperty.$VALUES : [Lstring/WidgetProperty;
INVOKEVIRTUAL [Lstring/WidgetProperty;.clone ()Ljava/lang/Object;
CHECKCAST [Lstring/WidgetProperty;
ARETURN

You don’t need to be a byte code expert to see the .clone() method invocation. So, every time you call the Enum.values() method — the array with the enum values is always copied.

The solution is pretty obvious — we can cache the Enum.values() method result like that:

public static final WidgetProperty[] values = values();

And use the cache field instead. Let’s check it with the benchmark:

I used the loop in that benchmark because, usually, the Enum.values() is used within the loop.

Results (lower score means faster):

The performance difference here is ~30%. That’s because the loop iteration takes some CPU as well. However, what is more important: after our fix, no allocation happens. And that’s even more important than gained speed improvement.

You may wonder, why Enum.values() is implemented in this way? Well, the explanation is quite simple. Without copy() method anyone could change the content of the returned array. And that could lead to many problems. So returning the same reference to the array with values that can be easily replaced isn’t the best idea. Immutable arrays would solve that issue, but Java doesn’t have one.

Yes, this change decreases the readability a bit and you don’t need to use it with every enum. It’s necessary only for really hot paths.

Check for example this PR for Apache Cassandra jdbc driver, mssql-jdbc driver PR or this PR for http server.

Conclusion

  • For hot paths, consider using your own Map cache of the Enum.valueOf() method. As an additional bonus, you could avoid throwing the Exception during the wrong input. That could be a game-changer for some flows
  • For hot paths, go with the cached Enum.values() field. This will allow you to decrease the allocation rate

Here is a source code of benchmarks, so that you can try it yourself.

Thank you for your attention, and stay tuned.

Previous post: Micro optimizations in Java. String.equalsIgnoreCase()

--

--