
High-performance primitive collections offering ArrayList/ArrayDeque, HashSet, and HashMap analogues that cut memory 4–5× and boost CPU 2–4× while avoiding boxing and minimizing dependency size.
A multi-platform library for high-performance primitive collections in the JVM/Kotlin ecosystem. FastCollect is highly memory efficient and performant, and is competitive with - if not faster than - every primitive collections library in the JVM ecosystem. See the benchmarking section for further details. Performance is complicated and any library that claims to be the de-facto fastest is probably not taking performance seriously. FastCollect is quite well tested, and has rounded off many of the sharp edges found in some collections libraries. First-class Java support is a stated goal - while not every feature is as idiomatic to use from Java as it would be from Kotlin, extensive work has gone into ensuring that it is not difficult or non-idiomatic either.
As a drop-in replacement for standard JRE collections, FastCollect generally reduces memory usage by 4–5× and improves CPU performance by 2-3×. FastCollect distinguishes itself with a much smaller footprint (supporting only necessary and useful collections), but performance comparable to or better than much larger and more complex libraries.
Note that performance has only been tested on JVM platforms.
FastCollect ships different artifacts depending on your target:
fastcollect
fastcollect-<platform> (i.e. fastcollect-jvm for JVM platforms)fastcollect-java - no dependency on the Kotlin Standard LibraryYou can add FastCollect as a dependency with:
Gradle:
implementation 'io.github.sooniln:fastcollect-jvm:5.0.0'Maven:
<dependency>
<groupId>io.github.sooniln</groupId>
<artifactId>fastcollect-jvm</artifactId>
<version>5.0.0</version>
</dependency>FastCollect can be used as a replacement for Kotlin standard library collections and should provide immediate memory and
CPU improvements without any further changes. As with most Kotlin libraries, you are encouraged to import the entire
library space via import io.github.sooniln.fastcollect.* so that extension functions 'just work'.
FastCollect can interact with normal Kotlin collections through the use of extension methods like [asList], [asSet], [asMap], and [asQueue], which produce a thin wrapper around the FastCollect collection which allows it to be used as a Kotlin collection, safe for use in any legacy APIs. Beware that using these wrappers may incur boxing penalties.
Using FastCollect types should be quite straightforward for anyone familiar with standard Kotlin/Java collections. FastCollect provides ArrayList/ArrayDeque, HashSet, HashMap, and PriorityQueue analogues that can store primitives (and in the case of maps, primitive keys with reference or primitive values).
[!NOTE] FastCollect currently only supports Int/Long keys for HashMap (all types of values are supported). This is out of a desire to reduce binary size and bloat by eliminating use cases that are unlikely to be very common or useful. If you feel you have a compelling use case that is not currently supported, please file a bug, as support is generally trivial to add.
Concrete primitive collection types supported:
Unsupported collection types:
On the JVM, primitive floating-point types obey IEEE floating-point comparisons (positive and negative zeros are equal, NaN is never equal to anything including itself). Boxed floating-point types however do not obey normal IEEE floating-point rules (positive and negative zeros are not equal, NaN can be equal to other Nan values).
In order to make the primitive collections in this library maximally useful, all collections internally implement equality as bit-wise equality. This means that equality used in this library is closer to JVM boxed type equality than primitive type equality. Within the collections for example, Float.NaN == Float.Nan and -0.0 != 0.0. Care must be used when interacting with these collections via external lambdas, for example:
import io.github.sooniln.fastcollect.*
var set = mutableFloatSetOf(Float.NaN)
// option 1 - removes NaN from the set
set.remove(Float.NaN)
// option 2 - does not remove NaN from the set
set.removeAll(value -> value == Float.NaN)Default JVM equality uses IEEE conventions for primitives. For this reason, FastCollect exposes publicly the comparison
methods it uses internally, as equalsRaw() and notEqualsRaw().
import io.github.sooniln.fastcollect.*
var set = mutableFloatSetOf(Float.NaN)
// option 1 - removes NaN from the set
set.remove(Float.NaN)
// option 2 - removes NaN from the set
set.removeAll(value -> value equalsRaw Float.NaN)The standard JRE libraries make reasonable efforts to throw ConcurrentModificationException if they detect collections being modified in inappropriate ways. This already only a best effort with no guarantees made, but FastCollect makes even less of an effort in the interests of performance. Do not expect FastCollect to throw ConcurrentModificationException every time you shoot yourself in the foot, only occasionally.
FastCollect introduces a new method of iterating through collections called Traverser. Traversable/Traverser are
roughly equivalent to Iterable/Iterator, but offer a slightly different API shape which (1) makes it easier to
implement iteration correctly (2) offers increased opportunities for compiler optimizations in complex implementations.
Benchmarking shows up to a 20% speed improvement when using Traverser vs Iterator for the same operations.
FastCollect still supports Iterator - all collection classes implement Iterable so they can be used in normal
for-each loops and anywhere that expects an Iterable. There also exists ListTraverser as an equivalent to
ListIterator.
Traverser APIs offer many of the same utility extension methods as Iterable, such as:
forEach())See the following examples section for further usage.
You'll find that FastCollect collection usage is pretty much exactly like Kotlin collection usage. A few (non-exhaustive) examples of common APIs follow:
import io.github.sooniln.fastcollect.*
// creating a list
var list = IntArrayList()
list = mutableIntListOf(1, 2, 3)
// get/set by index
var i = list[1]
list[1] = 2
// search for value in list
list.indexOf(1)
list.lastIndexOf(2)
list.contains(3)
// iterate over list
list.traverse { value ->
println(value)
}
// mutate list
list.add(5)
list.remove(5)
list.removeAt(0)
list.clear()
// other operations
list.sort()
list.shuffle()
list.fill(0)
// use the list somewhere a Kotlin list is required
legacyApi(list.asList())// creating a set
var set = IntHashSet() // create FastCollect set directly
set = mutableIntSetOf(1, 2, 3) // directly create FastCollect set
// search for presence in set
set.contains(3)
// iterate over set
set.traverse { value ->
println(value)
}
// mutate set
set.add(5)
set.remove(5)
set.clear()
// use the set somewhere a Kotlin set is required
legacyApi(set.asSet())// creating a map
var map = Int2IntHashMap() // create FastCollect map directly
map = mutableInt2IntMapOf(1 to 2, 2 to 4, 3 to 7) // directly create FastCollect map
// get/set by index
var v = map[1]
map[1] = 5
// search for key/value in map
map.containsKey(1)
map.containsValue(2)
// iterate over map
map.traverse { key, value ->
println("$key -> $value")
}
map.traverseKeys { key ->
println(key)
}
// mutate map
map.remove(5)
map.clear()
// other operations
map.getOrElse(1) { -1 }
// use the map somewhere a Kotlin map is required
legacyApi(map.asMap())// creating a priority queue
var priorityQueue = IntPriorityQueue(descending = true)
// mutate priority queue
priorityQueue.add(5)
priorityQueue.add(2)
priorityQueue.add(8)
priorityQueue.remove(5) // O(N)
priorityQueue.first() // returns 8
priorityQueue.removeFirst() // returns 8
priorityQueue.clear()
// iterate over priority queue (no ordering guarantees)
priorityQueue.traverse { value ->
println(value)
}
// use the priority queue somewhere a Kotlin queue is required
legacyApi(priorityQueue.asQueue())In addition to standard priority queues, FastCollect's AbstractPriorityQueue also offers extension points to easily build an indirect priority queue:
class IndirectPriorityQueue(private val priorities: IntArray): AbstractIntPriorityQueue() {
private val elementToIndexMap = IntArray(priorities.size) { -1 }
override fun isHigherPriority(element1: Int, element2: Int): Boolean {
return priorities[element1] > priorities[element2]
}
override fun onIndexChanged(element: Int, index: Int) {
elementToIndexMap[element] = index
}
override fun onRemoved(element: Int, index: Int) {
elementToIndexMap[element] = -1
}
fun update(element: Int) {
val index = elementToIndexMap[element]
if (index == -1) {
add(element)
} else {
updatePriority(index)
}
}
}This is a trivial example - more complex shapes are possible as well.
A key advantage of primitive collections is not just reduced CPU usage, but substantially lower memory usage, which has compounding benefits — more data fitting in CPU caches further reduces memory access latency.
A more detailed examination of performance and memory usage can be found in this post. In benchmarking, FastCollect unsurprisingly outperforms standard Kotlin collections, as well as many other primitive collection libraries.
A more detailed examination of memory usage can be found in the Memory Benchmarks doc. FastCollect has put effort into ensuring that not only are large collections memory efficient (which most primitive collections libraries accomplish), but also that small/empty collections are memory efficient (which some primitive collections are shockingly bad at).
FastCollect generates most of its collection classes from templates in order to reduce the amount of copy/pasted code present. Contrary to common practice, this project checks the generated code directly into the repository. While this is non-standard from a build pipeline perspective, this project has public APIs composed of generated code, and it is important for clients and users that the actual code (rather than just the generation templates) is viewable, searchable, and parseable within the repository itself.
A multi-platform library for high-performance primitive collections in the JVM/Kotlin ecosystem. FastCollect is highly memory efficient and performant, and is competitive with - if not faster than - every primitive collections library in the JVM ecosystem. See the benchmarking section for further details. Performance is complicated and any library that claims to be the de-facto fastest is probably not taking performance seriously. FastCollect is quite well tested, and has rounded off many of the sharp edges found in some collections libraries. First-class Java support is a stated goal - while not every feature is as idiomatic to use from Java as it would be from Kotlin, extensive work has gone into ensuring that it is not difficult or non-idiomatic either.
As a drop-in replacement for standard JRE collections, FastCollect generally reduces memory usage by 4–5× and improves CPU performance by 2-3×. FastCollect distinguishes itself with a much smaller footprint (supporting only necessary and useful collections), but performance comparable to or better than much larger and more complex libraries.
Note that performance has only been tested on JVM platforms.
FastCollect ships different artifacts depending on your target:
fastcollect
fastcollect-<platform> (i.e. fastcollect-jvm for JVM platforms)fastcollect-java - no dependency on the Kotlin Standard LibraryYou can add FastCollect as a dependency with:
Gradle:
implementation 'io.github.sooniln:fastcollect-jvm:5.0.0'Maven:
<dependency>
<groupId>io.github.sooniln</groupId>
<artifactId>fastcollect-jvm</artifactId>
<version>5.0.0</version>
</dependency>FastCollect can be used as a replacement for Kotlin standard library collections and should provide immediate memory and
CPU improvements without any further changes. As with most Kotlin libraries, you are encouraged to import the entire
library space via import io.github.sooniln.fastcollect.* so that extension functions 'just work'.
FastCollect can interact with normal Kotlin collections through the use of extension methods like [asList], [asSet], [asMap], and [asQueue], which produce a thin wrapper around the FastCollect collection which allows it to be used as a Kotlin collection, safe for use in any legacy APIs. Beware that using these wrappers may incur boxing penalties.
Using FastCollect types should be quite straightforward for anyone familiar with standard Kotlin/Java collections. FastCollect provides ArrayList/ArrayDeque, HashSet, HashMap, and PriorityQueue analogues that can store primitives (and in the case of maps, primitive keys with reference or primitive values).
[!NOTE] FastCollect currently only supports Int/Long keys for HashMap (all types of values are supported). This is out of a desire to reduce binary size and bloat by eliminating use cases that are unlikely to be very common or useful. If you feel you have a compelling use case that is not currently supported, please file a bug, as support is generally trivial to add.
Concrete primitive collection types supported:
Unsupported collection types:
On the JVM, primitive floating-point types obey IEEE floating-point comparisons (positive and negative zeros are equal, NaN is never equal to anything including itself). Boxed floating-point types however do not obey normal IEEE floating-point rules (positive and negative zeros are not equal, NaN can be equal to other Nan values).
In order to make the primitive collections in this library maximally useful, all collections internally implement equality as bit-wise equality. This means that equality used in this library is closer to JVM boxed type equality than primitive type equality. Within the collections for example, Float.NaN == Float.Nan and -0.0 != 0.0. Care must be used when interacting with these collections via external lambdas, for example:
import io.github.sooniln.fastcollect.*
var set = mutableFloatSetOf(Float.NaN)
// option 1 - removes NaN from the set
set.remove(Float.NaN)
// option 2 - does not remove NaN from the set
set.removeAll(value -> value == Float.NaN)Default JVM equality uses IEEE conventions for primitives. For this reason, FastCollect exposes publicly the comparison
methods it uses internally, as equalsRaw() and notEqualsRaw().
import io.github.sooniln.fastcollect.*
var set = mutableFloatSetOf(Float.NaN)
// option 1 - removes NaN from the set
set.remove(Float.NaN)
// option 2 - removes NaN from the set
set.removeAll(value -> value equalsRaw Float.NaN)The standard JRE libraries make reasonable efforts to throw ConcurrentModificationException if they detect collections being modified in inappropriate ways. This already only a best effort with no guarantees made, but FastCollect makes even less of an effort in the interests of performance. Do not expect FastCollect to throw ConcurrentModificationException every time you shoot yourself in the foot, only occasionally.
FastCollect introduces a new method of iterating through collections called Traverser. Traversable/Traverser are
roughly equivalent to Iterable/Iterator, but offer a slightly different API shape which (1) makes it easier to
implement iteration correctly (2) offers increased opportunities for compiler optimizations in complex implementations.
Benchmarking shows up to a 20% speed improvement when using Traverser vs Iterator for the same operations.
FastCollect still supports Iterator - all collection classes implement Iterable so they can be used in normal
for-each loops and anywhere that expects an Iterable. There also exists ListTraverser as an equivalent to
ListIterator.
Traverser APIs offer many of the same utility extension methods as Iterable, such as:
forEach())See the following examples section for further usage.
You'll find that FastCollect collection usage is pretty much exactly like Kotlin collection usage. A few (non-exhaustive) examples of common APIs follow:
import io.github.sooniln.fastcollect.*
// creating a list
var list = IntArrayList()
list = mutableIntListOf(1, 2, 3)
// get/set by index
var i = list[1]
list[1] = 2
// search for value in list
list.indexOf(1)
list.lastIndexOf(2)
list.contains(3)
// iterate over list
list.traverse { value ->
println(value)
}
// mutate list
list.add(5)
list.remove(5)
list.removeAt(0)
list.clear()
// other operations
list.sort()
list.shuffle()
list.fill(0)
// use the list somewhere a Kotlin list is required
legacyApi(list.asList())// creating a set
var set = IntHashSet() // create FastCollect set directly
set = mutableIntSetOf(1, 2, 3) // directly create FastCollect set
// search for presence in set
set.contains(3)
// iterate over set
set.traverse { value ->
println(value)
}
// mutate set
set.add(5)
set.remove(5)
set.clear()
// use the set somewhere a Kotlin set is required
legacyApi(set.asSet())// creating a map
var map = Int2IntHashMap() // create FastCollect map directly
map = mutableInt2IntMapOf(1 to 2, 2 to 4, 3 to 7) // directly create FastCollect map
// get/set by index
var v = map[1]
map[1] = 5
// search for key/value in map
map.containsKey(1)
map.containsValue(2)
// iterate over map
map.traverse { key, value ->
println("$key -> $value")
}
map.traverseKeys { key ->
println(key)
}
// mutate map
map.remove(5)
map.clear()
// other operations
map.getOrElse(1) { -1 }
// use the map somewhere a Kotlin map is required
legacyApi(map.asMap())// creating a priority queue
var priorityQueue = IntPriorityQueue(descending = true)
// mutate priority queue
priorityQueue.add(5)
priorityQueue.add(2)
priorityQueue.add(8)
priorityQueue.remove(5) // O(N)
priorityQueue.first() // returns 8
priorityQueue.removeFirst() // returns 8
priorityQueue.clear()
// iterate over priority queue (no ordering guarantees)
priorityQueue.traverse { value ->
println(value)
}
// use the priority queue somewhere a Kotlin queue is required
legacyApi(priorityQueue.asQueue())In addition to standard priority queues, FastCollect's AbstractPriorityQueue also offers extension points to easily build an indirect priority queue:
class IndirectPriorityQueue(private val priorities: IntArray): AbstractIntPriorityQueue() {
private val elementToIndexMap = IntArray(priorities.size) { -1 }
override fun isHigherPriority(element1: Int, element2: Int): Boolean {
return priorities[element1] > priorities[element2]
}
override fun onIndexChanged(element: Int, index: Int) {
elementToIndexMap[element] = index
}
override fun onRemoved(element: Int, index: Int) {
elementToIndexMap[element] = -1
}
fun update(element: Int) {
val index = elementToIndexMap[element]
if (index == -1) {
add(element)
} else {
updatePriority(index)
}
}
}This is a trivial example - more complex shapes are possible as well.
A key advantage of primitive collections is not just reduced CPU usage, but substantially lower memory usage, which has compounding benefits — more data fitting in CPU caches further reduces memory access latency.
A more detailed examination of performance and memory usage can be found in this post. In benchmarking, FastCollect unsurprisingly outperforms standard Kotlin collections, as well as many other primitive collection libraries.
A more detailed examination of memory usage can be found in the Memory Benchmarks doc. FastCollect has put effort into ensuring that not only are large collections memory efficient (which most primitive collections libraries accomplish), but also that small/empty collections are memory efficient (which some primitive collections are shockingly bad at).
FastCollect generates most of its collection classes from templates in order to reduce the amount of copy/pasted code present. Contrary to common practice, this project checks the generated code directly into the repository. While this is non-standard from a build pipeline perspective, this project has public APIs composed of generated code, and it is important for clients and users that the actual code (rather than just the generation templates) is viewable, searchable, and parseable within the repository itself.