Java Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we’ll be looking at the FastUtil library.

First, we’ll code some examples of its type-specific collections.

Then, we’ll analyze the performance that gives FastUtil its name.

Finally, let’s take a peek at FastUtil‘s BigArray utilities.

2. Features

The FastUtil Java library seeks to extend the Java Collections Framework. It provides type-specific maps, sets, lists and queues with a smaller memory footprint and fast access and insertion. FastUtil also provides a set of utilities for working with and manipulating large (64-bit) arrays, sets and lists.

The library also includes a multitude of practical Input/Output classes for binary and text files.

Its latest release, FastUtil 8, also released a host of type-specific functions, extending the JDK’s Functional Interfaces.

2.1. Speed

In many cases, the FastUtil implementations are the fastest available. The authors have even provided their own in-depth benchmark report, comparing it against similar libraries include HPPC and Trove.

In this tutorial, we’ll look to define our own benchmarks using the Java Microbench Harness (JMH).

3. Full Sized Dependency

On top of the usual JUnit dependency, we’ll be using the FastUtils and JMH dependencies in this tutorial.

We’ll need the following dependencies in our pom.xml file:

<dependency>
    <groupId>it.unimi.dsi</groupId>
    <artifactId>fastutil</artifactId>
    <version>8.2.2</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.19</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.19</version>
    <scope>test</scope>
</dependency>

Or for Gradle users:

testCompile group: 'org.openjdk.jmh', name: 'jmh-core', version: '1.19'
testCompile group: 'org.openjdk.jmh', name: 'jmh-generator-annprocess', version: '1.19'
compile group: 'it.unimi.dsi', name: 'fastutil', version: '8.2.2'

3.1. Customized Jar File

Due to the lack of generics, FastUtils generates a large number of type-specific classes. And unfortunately, this leads to a huge jar file.

However, luckily for us, FastUtils includes a find-deps.sh script which allows generation of smaller, more focused jars comprising of only the classes we want to use in our application.

4. Type-Specific Collections

Before we begin, let’s take a quick peek at the simple process of instantiating a type-specific collection. Let’s pick a HashMap that stores keys and values using doubles. 

For this purpose, FastUtils provides a Double2DoubleMap interface and a Double2DoubleOpenHashMap implementation:

Double2DoubleMap d2dMap = new Double2DoubleOpenHashMap();

Now that we’ve instantiated our class, we can simply populate data as we would with any Map from the Java Collections API:

d2dMap.put(2.0, 5.5);
d2dMap.put(3.0, 6.6);

Finally, we can check that the data has been added correctly:

assertEquals(5.5, d2dMap.get(2.0));

4.1. Performance

FastUtils focuses on its performant implementations. In this section, we’ll make use of the JMH to verify that fact. Let’s compare the Java Collections HashSet<Integer> implementation against FastUtil’s IntOpenHashSet.

First, let’s see how to implement the IntOpenHashSet:

@Param({"100", "1000", "10000", "100000"})
public int setSize;

@Benchmark
public IntSet givenFastUtilsIntSetWithInitialSizeSet_whenPopulated_checkTimeTaken() {
    IntSet intSet = new IntOpenHashSet(setSize);
    for(int i = 0; i < setSize; i++) {
        intSet.add(i);
    }
    return intSet; 
}

Above, we’ve simply declared the IntOpenHashSet implementation of the IntSet interface. We’ve also declared the initial size setSize with the @Param annotation.

Put simply, these numbers are fed into JMH to produce a series of benchmark tests with different set sizes.

Next, let’s do the same thing using the Java Collections implementation:

@Benchmark
public Set<Integer> givenCollectionsHashSetWithInitialSizeSet_whenPopulated_checkTimeTaken() {
    Set<Integer> intSet = new HashSet<>(setSize);
    for(int i = 0; i < setSize; i++) {
        intSet.add(i);
    }
    return intSet;
}

Finally, let’s run the benchmark and compare the two implementations:

Benchmark                                     (setSize)  Mode  Cnt     Score   Units
givenCollectionsHashSetWithInitialSizeSet...        100  avgt    2     1.460   us/op
givenCollectionsHashSetWithInitialSizeSet...       1000  avgt    2    12.740   us/op
givenCollectionsHashSetWithInitialSizeSet...      10000  avgt    2   109.803   us/op
givenCollectionsHashSetWithInitialSizeSet...     100000  avgt    2  1870.696   us/op
givenFastUtilsIntSetWithInitialSizeSet...           100  avgt    2     0.369   us/op
givenFastUtilsIntSetWithInitialSizeSet...          1000  avgt    2     2.351   us/op
givenFastUtilsIntSetWithInitialSizeSet...         10000  avgt    2    37.789   us/op
givenFastUtilsIntSetWithInitialSizeSet...        100000  avgt    2   896.467   us/op

These results make it clear the FastUtils implementation is much more performant than the Java Collections alternative.

5. Big Collections

Another important feature of FastUtils is the ability to use 64-bit arrays. Arrays in Java, by default, are limited to 32 bits.

To get started, let’s take a look at the BigArrays class for Integer types. IntBigArrays provides static methods for working with 2-dimensional Integer arrays. By using these provided methods, we can essentially wrap our array into a more user-friendly 1-dimensional array.

Let’s take a look at how this works.

First, we’ll start by initializing a 1-dimensional array, and converting it into a 2-dimensional array using IntBigArray’s wrap method:

int[] oneDArray = new int[] { 2, 1, 5, 2, 1, 7 };
int[][] twoDArray = IntBigArrays.wrap(oneDArray.clone());

We should make sure to use the clone method to ensure a deep copy of the array.

Now, as we’d do with a List or a Map, we can gain access to the elements using the get method:

int firstIndex = IntBigArrays.get(twoDArray, 0);
int lastIndex = IntBigArrays.get(twoDArray, IntBigArrays.length(twoDArray)-1);

Finally, let’s add some checks to ensure our IntBigArray returns the correct values:

assertEquals(2, firstIndex);
assertEquals(7, lastIndex);

6. Conclusion

In this article, we’ve taken a dive into FastUtils core features.

We looked at some of the type-specific collections that FastUtil offers, before playing around with some BigCollections.

As always, the code can be found over on GitHub

Java bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE