Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Java Holiday Calendar 2016 (Day 19): Speed Up Your Enums

DZone's Guide to

Java Holiday Calendar 2016 (Day 19): Speed Up Your Enums

The .values() method might seem speedy, but it actually creates a copy of your array, adding to your overhead. Doing a bit of extra work yourself will help in the long run.

· Java Zone
Free Resource

The single app analytics solutions to take your web and mobile apps to the next level.  Try today!  Brought to you in partnership with CA Technologies

Image title


Today's tip is about Enum performance. A large number of Java programmers think Enums have a very fast method called .values() that returns all the Enums. Heck, it is even an array being returned, so it should really be super fast? Well, not really...

Suppose we've declared an enum like this:

public enum Car {
    TESLA, VOLVO, TOYOTA;
}


Then it turns out Java will generate an equivalent to the following under the hood:

public static Car[] values() {
    return (Car[])$VALUES.clone();
}


This means each time we are calling values(), we are creating a copy of the internal VALUES array. The reason for this is that an array cannot be protected from being overwritten by user code. Thus, a new copy must be provided for each call to guarantee state integrity. The JVM can mitigate the problem under certain circumstances, but if we want to be absolutely sure no objects are created, we must implement our own equivalent to values(). This can be done like this:

public enum Car {
    TESLA, VOLVO, TOYOTA;

    private static final List<Car> VALUE_LIST = Stream.of(values())
            .collect(collectingAndThen(toList(), Collections::unmodifiableList));

    public static List<Car> valuesAsList() {
        return VALUE_LIST;
    }
}


Note that the internal VALUE_LIST is unmodifiable and that this is important, or else we cannot expose it directly via the valuesAsList() method.

Now we can use our modified Enum like this with no performance overhead:

Car.valuesAsList().forEach(System.out::println);


Update

I ran the following JMH benchmark (thanks Joe for the proposal):

@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Threads(Threads.MAX)
@Fork(3)
@State(Scope.Benchmark)
public class CarBenchmark {


    @Benchmark
    public Car[] valueBenchmark() {
        return Car.values();
    }

    @Benchmark
    public List<Car> valueAsListBenchmark() {
        return Car.valuesAsList();
    }

}

And it came out with the following result:

Result "valueBenchmark":
  163315824.656 ±(99.9%) 23937389.480 ops/s [Average]
  (min, avg, max) = (134687130.170, 163315824.656, 214846495.652), stdev = 22391048.973
  CI (99.9%): [139378435.177, 187253214.136] (assumes normal distribution)


# Run complete. Total time: 00:01:03

Benchmark                           Mode  Cnt           Score          Error  Units
CarBenchmark.valueAsListBenchmark  thrpt   15  1224047670.454 ± 49040744.220  ops/s
CarBenchmark.valueBenchmark        thrpt   15   163315824.656 ± 23937389.480  ops/s

This indicates that valuesAsList() can be much faster than values(). In this case, it was almost ten times faster.

Update 2

If we look at a broader perspective than just getting the value() or getValueAsList() and actually consume the values we are iterating over, what is then the gain?

If we run the following tests:

private static final Consumer<? super Car> BLACKHOLE = c -> {}; 

@Benchmark
public void valueForEachBenchmark() {
    for (Car car: Car.values()) {
        BLACKHOLE.accept(car);
    }
}

@Benchmark
public void valueAsListForEachBenchmark() {
    Car.valuesAsList().forEach(BLACKHOLE);
}


Then valuesAsList() is still five times faster than values().

Result "valueForEachBenchmark":
  177293926.439 ±(99.9%) 10702768.085 ops/s [Average]
  (min, avg, max) = (155995378.714, 177293926.439, 197287067.497), stdev = 10011375.908
  CI (99.9%): [166591158.355, 187996694.524] (assumes normal distribution)


# Run complete. Total time: 00:00:49

Benchmark                                  Mode  Cnt            Score           Error  Units
CarBenchmark.valueAsListForEachBenchmark  thrpt   15    706004496.271 ±  77237061.646  ops/s
CarBenchmark.valueForEachBenchmark        thrpt   15    177293926.439 ±  10702768.085  ops/s


As the number of elements in the Enum increases, the valuesAsList() gets even better relative to values(). See the following graph:

Image title

X-axis: Number of Enum elements, Y-axis: operations per second. Higher is better.

N.B. Replacing the valueForEachBenchmark's for-each loop with a for (int i=0, i<values.length;i++) {...values[i]...} does not affect the performance noticable.


Follow the Java Holiday Calendar 2016 with small tips and tricks all the way through the winter holiday season.

CA App Experience Analytics, a whole new level of visibility. Learn more. Brought to you in partnership with CA Technologies.

Topics:
java ,enums ,java performance

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}