Why is Java's switch enum so painfully slow on the first run compared to its 'if' equivalent?
I'm aware that the JVM needs to "warm-up" before performance can be reliably measured. Therefore every first call is much much slower than any subsequent one. This does not mean we cannot measure the performance based on every first run.
The criteria for the test are:
- Always perform a fresh run.
- Measure time in nanoseconds to execute a single function which always returns an integer based on passed value evaluated either by
if
statements or aswitch
statement. - Store returned value and print it in the end, so it doesn't get discarded in the process.
I tested enums first and expected a slight difference in performance.
Instead I got an average of:
77596
nanoseconds - on if585232
nanoseconds - on switch
I wanted to see if only enums have this unfavorable property, so I also tested it with integers and strings (since Java 7 it is possible to use strings in switch statements)
INTS:
2308
nanoseconds - on if1950
nanoseconds - on switch
STRINGS:
8517
nanoseconds - on if8322
nanoseconds - on switch
Both these tests yield very similar results, suggesting that if and switch statements are equivalent, very similar or equally good on every run, however this is not the case with enums.
I tested this both on Windows and Linux with both Java 8 and Java 17.
Here is the switch enum code:
public class SwitchEnum{
public static void main(String[] args){
long st = System.nanoTime();
int val = getValue(Day.FRIDAY);
long en = System.nanoTime();
System.out.println("SwitchEnum perf nano: " + (en - st));
System.out.println("Sum: " + val);
}
public static int getValue(Day day){
switch (day){
case MONDAY:
return 7;
case TUESDAY:
return 3;
case WEDNESDAY:
return 5;
case THURSDAY:
return 2;
case FRIDAY:
return 1;
case SATURDAY:
return 6;
case SUNDAY:
return 4;
default:
throw new RuntimeException();
}
}
}
Here is the if enum code:
public class IfEnum{
public static void main(String[] args){
long st = System.nanoTime();
int val = getValue(Day.FRIDAY);
long en = System.nanoTime();
System.out.println("IfEnum perf nano: " + (en - st));
System.out.println("Sum: " + val);
}
public static int getValue(Day day){
if (day == Day.MONDAY){
return 7;
}else if (day == Day.TUESDAY){
return 3;
}else if (day == Day.WEDNESDAY){
return 5;
}else if (day == Day.THURSDAY){
return 2;
}else if (day == Day.FRIDAY){
return 1;
}else if (day == Day.SATURDAY){
return 6;
}else if (day == Day.SUNDAY){
return 4;
}else{
throw new RuntimeException();
}
}
}
And the enum:
public enum Day{
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
I also tested this in C and C# to see if switch statements on enums have a significant performance drawback compared to its if equivalents - there was none. I've also noticed that if we provide an instruction in 'default' or equivalent 'else' the performance also increases so I included it in all tests.
This question is not about the typical "if vs switch" battle, but rather what's going on with enums and switch statements.
In any case why should the switch with enums be on average 7 times slower than it's equivalent? What could be the cause of this?
It seems like I have been misunderstood. In truth the original enum was completely different, as I was trying to find the culprit of the 'unreasonable overhead' I came up with this benchmark.
Funnily enough, warming up the JVM doesn't help the performance of that function at all.
You can put some nested loops before the method in question:
public static void main(String[] args) throws InterruptedException{
for (int i = 0; i < 1000; i++){
for (int j = 0; j < 1000; j++){
System.out.println(j);
}
System.out.println(i);
}
Thread.sleep(100);
for (int i = 0; i < 1000; i++){
System.out.println(i);
}
long st = System.nanoTime();
int val = getValue(Day.FRIDAY);
long en = System.nanoTime();
System.out.println("SwitchEnum perf nano: " + (en - st));
System.out.println("Sum: " + val);
}
The only thing that matters is if it was already called. Every subsequent call is optimized. Whether it's a constructor, function or an object's method. The fact is that if you're initializing a framework you will only call the 'initialize()' method once (which will in turn call other methods on its way). In this particular case the only thing you'd care about is the performance of the first invocation of a function. Let's suppose that your framework calls 8000 methods when it's first launched. Each method takes 1ms to execute, so it propagates to 8 seconds on every run. And the Java community is simply going to say "you're benchmarking it incorrectly"? No. This is how long it takes to get that particular framework up and running. Naturally performance is lost here and there. You can always make it faster and better. There is no reason for the switch enum statement to add 0.6ms to the clock given that its 'if' equivalent takes 0.1ms.
So here I am asking, what is the source of this overhead?