EnumMap Performance Analysis (Java)

There is a special type of java.util.Map that we can use when the keys are enums: java.util.EnumMap.

It is my experience that developers are often unaware of the EnumMap and will tend instead to use HashMap or LinkedHashMap even when they are using enums as keys.

Internal mechanisms

HashMap is represented internally as a dynamically resized table of hash bin nodes. The keys must be hashed, and the get and put algorithms have a reasonable degree of complexity.

LinkedHashMap extends HashMap. LinkedHashMap uses a hash table combined with a doubly-linked list to give a predictable iteration order. For this reason they have even more complexity than basic hash maps.

EnumMap is based on the following fact: If we know the entire set of all possible keys of a Map in advance, then we can use a more efficient algorithm than if we need to support any arbitrary set of keys. Enum maps are represented internally as arrays, which allows for an efficient implementation.

Performance comparison

I decided to investigate the performance difference between the EnumMap and these two kinds of hash map. The results clearly show that the EnumMap has a significant performance advantage over hash maps. These performance advantages become more apparent the more values there are in the enum, and the more read-cycles we perform on the map instance.

Test approach

For this test I am using Java 17.

First, I defined four different enums, with the following number of values respectively: 3, 5, 10 and 20. These sizes I felt would give a broad sample of the typical size of enums.

I then wrote the following generic test function to test any type of enum with any type of map:

private static final int ITERATIONS = 1_000_000;
private static final int READ_CYCLES = 10;

// ...

private <M extends Map<E, Integer>, E extends Enum<E>> void testMapPerformance(
		E[] values, Supplier<M> mapSupplier, String mapName) {
	
	long total = 0;

	Instant start = Instant.now();

	for (int i = 0; i < ITERATIONS; i++) {
		Map<E, Integer> map = mapSupplier.get();
		for (int j = 0; j < values.length; j++) {
			map.put(values[j], j);
		}
		for (int r = 0; r < READ_CYCLES; r++) {
			for (E value : values) {
				total += map.get(value);
			}
		}
	}

	Instant end = Instant.now();

	// ...
}

The function times how long it takes to perform one million iterations of:

  1. create a new map;
  2. fill it with with each enum value as the map entry key and the value’s index as the map entry value; and
  3. read each value from the map a set number of times as defined by the READ_CYCLES constant.

I then wrote a dozen test methods, four for each of the different map types EnumMap, HashMap and LinkedHashMap. Each of the maps’ four test methods tests the performance using the different enum sizes of 3, 5, 10 and 20 respectively.

Adding a StringBuilder with CSV generating formatting then made it easy to tabulate the results of each test execution. I created a spreadsheet for the analysis and then executed 10 test runs for each of the following values of READ_CYCLES: 1, 3, 5, 10 and 20. There were therefore 50 test runs in total.

Results

Here are the results of the analysis, with times given in milliseconds:

Results for READ_CYCLES == 1
Results for READ_CYCLES == 3
Results for READ_CYCLES == 5
Results for READ_CYCLES == 10
Results for READ_CYCLES == 20

Discussion

In nearly every case, EnumMap shows a performance improvement when compared to the two hash maps. Similarly, in most cases, HashMap shows a performance improvement when compared to LinkedHashMap.

You may have noticed that when the read cycles are low and the number of enum values is also low, the performance difference is less noticeable. Indeed, if read cycles is 1 and enum value count is 3 then HashMap has a marginal performance advantage when compared to EnumMap! This is because there is a slightly higher overhead when it comes to creating an EnumMap relative to creating a HashMap, and if sufficient use is not made of the read efficiencies of the EnumMap, the creation overhead predominates.

However, in most cases it would still be advisable to use EnumMap whenever possible, since this is an edge case and the performance difference is minimal. The knowledge shared by using EnumMap whenever possible is arguably worth a tiny performance drop in such situations.

I noticed that for 20 read cycles, there appears to be an anomalous result for enums with 5 values for HashMap when compared to LinkedHashMap, recording averages of 1118.4ms and 908.2ms respectively. I do not intend to investigate this as it is outside the scope of this article, but it is worth noting as it may be of interest to someone if it is a real effect and not simply a random chance outlier.

Conclusion

In conclusion, when you want to use a Map with enums as keys, you should probably use an EnumMap!

However, if iteration order is important to you, and for some reason you wanted to iterate in a different order from the order in which the enum values are declared (otherwise known as their ‘ordinal’) then you should use a LinkedHashMap. In such a case you might also consider whether you could simply re-order the values in the enum’s definition to make use of the EnumMap here too.

Leave a Reply

Your email address will not be published. Required fields are marked *