Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 9 collections' convenience factory methods as an alternative to collection literals

Consider this method (just for illustration):

boolean isSmallNumber(String s) {
    return (n in ["one", "two", "three", "four"]);
}

That, of course, is not Java, but it could be in your favourite alternative language that supports collection literals, such as Groovy or Kotlin. The expression is succinct, and, just like string literals, the compiler is allowed to put the collection literal in some static storage area(maybe even "intern()" it).

Now enter Java 9:

boolean isSmallNumber(String s) {
    return Set.of("one", "two", "three", "four").contains(s);
}

That's also succinct, but unfortunately it allocates a new Set on the heap every time you call it, and then immediately makes it available for garbage collection.

You can, of course, define a collection constant:

private static final Set<String> SMALL_NUMBERS = Set.of(...);

But then this definition could be a thousand lines away from the method definition in a large class, and you might not be able to think of a good descriptive name for it whereas a literal might be clearer (in this hypothetical case).

So, if I use Set.of(...) inside the method, will the JIT compiler optimize away the creation of a new object each time the method is called?

like image 673
DodgyCodeException Avatar asked Oct 05 '17 13:10

DodgyCodeException


People also ask

Which of the following method is correct about set in Java 9?

Both of the options are correct. Q 2 - Which of the following method is correct about Set in Java 9? A - For Set interfaces, of(...) method is overloaded to have 0 to 10 parameters.

What are collection literals?

A collection literal is a syntactic expression form that evaluates to an aggregate type, such as an array, List , or Map . Many languages support collection literals.

What are the main goals of Java 9?

The main goals for Java 9 are to: Make the Java Standard Edition platform, and the JDK, more navigable to scale down for small computing devices. Improve the overall security and maintain not only the JDK but the Java Implementations in general. Allow overall improved application performance.

What is collection string in Java?

The Collection in Java is a framework that provides an architecture to store and manipulate the group of objects. Java Collections can achieve all the operations that you perform on a data such as searching, sorting, insertion, manipulation, and deletion. Java Collection means a single unit of objects.


1 Answers

I've crafted a simple JMH benchmark:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class Temp {

    private Object value;

    @Setup
    public void setUp() {
        value = 50;
    }

    @Benchmark
    public boolean list1() {
        return List.of("one").contains(value);
    }

    @Benchmark
    public boolean list2() {
        return List.of("one", "two").contains(value);
    }

    @Benchmark
    public boolean list3() {
        return List.of("one", "two", "three").contains(value);
    }

    @Benchmark
    public boolean list4() {
        return List.of("one", "two", "three", "four").contains(value);
    }

    @Benchmark
    public boolean set1() {
        return Set.of("one").contains(value);
    }

    @Benchmark
    public boolean set2() {
        return Set.of("one", "two").contains(value);
    }

    @Benchmark
    public boolean set3() {
        return Set.of("one", "two", "three").contains(value);
    }

    @Benchmark
    public boolean set4() {
        return Set.of("one", "two", "three", "four").contains(value);
    }
}

After running the benchmark with -prof gc, I can make the following conclusion: JIT optimizes list1, list2, set1, set2, but not list3, list4, set3, set4 [1]

This seems totally reasonable because for N >= 3 listN/setN create more complex List/Set implementations than for N <= 2.

List implementation for 2 elements:

static final class List2<E> extends AbstractImmutableList<E> {
    private final E e0;
    private final E e1;
    ...
}

List implementation for 3 or more elements:

static final class ListN<E> extends AbstractImmutableList<E> {
    private final E[] elements;
    ...
}

ListN contains another level of indirection (an array) which apparently makes escape analysis much harder.


JMH output (slightly changed to fit the page):

Benchmark                  Mode  Cnt     Score      Error   Units
list1                      avgt    5     3,075 ?    1,165   ns/op
list1:·gc.alloc.rate       avgt    5     0,131 ?    1,117  MB/sec
list1:·gc.alloc.rate.norm  avgt    5    ? 10??               B/op
list1:·gc.count            avgt    5       ? 0             counts

list2                      avgt    5     3,161 ?    0,543   ns/op
list2:·gc.alloc.rate       avgt    5     0,494 ?    3,065  MB/sec
list2:·gc.alloc.rate.norm  avgt    5     0,001 ?    0,003    B/op
list2:·gc.count            avgt    5       ? 0             counts

list3                      avgt    5    33,094 ?    4,402   ns/op
list3:·gc.alloc.rate       avgt    5  6316,970 ?  750,240  MB/sec
list3:·gc.alloc.rate.norm  avgt    5    64,016 ?    0,089    B/op
list3:·gc.count            avgt    5   169,000             counts
list3:·gc.time             avgt    5   154,000                 ms

list4                      avgt    5    32,718 ?    3,657   ns/op
list4:·gc.alloc.rate       avgt    5  6403,487 ?  729,235  MB/sec
list4:·gc.alloc.rate.norm  avgt    5    64,004 ?    0,017    B/op
list4:·gc.count            avgt    5   165,000             counts
list4:·gc.time             avgt    5   146,000                 ms

set1                       avgt    5     3,218 ?    0,822   ns/op
set1:·gc.alloc.rate        avgt    5     0,237 ?    1,973  MB/sec
set1:·gc.alloc.rate.norm   avgt    5    ? 10??               B/op
set1:·gc.count             avgt    5       ? 0             counts

set2                       avgt    5     7,087 ?    2,029   ns/op
set2:·gc.alloc.rate        avgt    5     0,647 ?    4,755  MB/sec
set2:·gc.alloc.rate.norm   avgt    5     0,001 ?    0,010    B/op
set2:·gc.count             avgt    5       ? 0             counts

set3                       avgt    5    88,460 ?   16,834   ns/op
set3:·gc.alloc.rate        avgt    5  3565,506 ?  687,900  MB/sec
set3:·gc.alloc.rate.norm   avgt    5    96,000 ?    0,001    B/op
set3:·gc.count             avgt    5   143,000             counts
set3:·gc.time              avgt    5   108,000                 ms

set4                       avgt    5   118,652 ?   41,035   ns/op
set4:·gc.alloc.rate        avgt    5  2887,359 ?  920,180  MB/sec
set4:·gc.alloc.rate.norm   avgt    5   104,000 ?    0,001    B/op
set4:·gc.count             avgt    5   136,000             counts
set4:·gc.time              avgt    5    94,000                 ms

[1] Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)

like image 85
ZhekaKozlov Avatar answered Sep 21 '22 15:09

ZhekaKozlov