Skip to content

Commit d7d0e47

Browse files
chore(syncmap): add benchmark results and readme
1 parent 12b08f2 commit d7d0e47

File tree

5 files changed

+285
-2
lines changed

5 files changed

+285
-2
lines changed

collections-fastutil/benchmarks.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Benchmarks
2+
3+
## Int2ObjectSyncMap
4+
5+
The following benchmarks are a comparison of `Int2ObjectSyncMap` vs `Int2ObjectMaps#synchronize()`.
6+
7+
### Summary
8+
9+
The `Int2ObjectSyncMap` has similar performance characteristics of the `Int2ObjectMaps#synchronize()`,
10+
with an advantage in write speed.
11+
12+
The following results were recorded on an _M4 Macbook Pro_, using 8 threads and
13+
100,000 entries for each benchmark:
14+
15+
- `Int2ObjectSyncMap` **get()** speed is...
16+
- 5693.1% **FASTER** than `Int2ObjectMaps#synchronize()` for an empty map.
17+
- 6365.4% **FASTER** than `Int2ObjectMaps#synchronize()` for a presized empty map.
18+
- 12794.7% **FASTER** than `Int2ObjectMaps#synchronize()` for a full map.
19+
20+
- `Int2ObjectSyncMap` **put()** speed is...
21+
- 1052.4% **FASTER** than `Int2ObjectMaps#synchronize()` for an empty map.
22+
- 869.2% **FASTER** than `Int2ObjectMaps#synchronize()` for a presized empty map.
23+
- 2309.5% **FASTER** than `Int2ObjectMaps#synchronize()` for a full map.
24+
25+
- `Int2ObjectSyncMap` **put()** then **get()** speed is...
26+
- 3221.0% **FASTER** than `Int2ObjectMaps#synchronize()` for an empty map.
27+
- 3342.1% **FASTER** than `Int2ObjectMaps#synchronize()` for a presized empty map.
28+
- 4000.0% **FASTER** than `Int2ObjectMaps#synchronize()` for a full map.
29+
30+
### Results
31+
32+
```txt
33+
Benchmark (implementation) (mode) (prime) (readPercentage) (size) Mode Cnt Score Error Units
34+
Int2ObjectSyncMapBenchmark.get_only SyncMap none false 50 100000 thrpt 5 1.680 ± 0.001 ops/ns
35+
Int2ObjectSyncMapBenchmark.get_only SyncMap none true 50 100000 thrpt 5 1.680 ± 0.001 ops/ns
36+
Int2ObjectSyncMapBenchmark.get_only SyncMap presize false 50 100000 thrpt 5 1.681 ± 0.003 ops/ns
37+
Int2ObjectSyncMapBenchmark.get_only SyncMap presize true 50 100000 thrpt 5 1.679 ± 0.003 ops/ns
38+
Int2ObjectSyncMapBenchmark.get_only SyncMap prepopulate false 50 100000 thrpt 5 1.752 ± 1.043 ops/ns
39+
Int2ObjectSyncMapBenchmark.get_only SyncMap prepopulate true 50 100000 thrpt 5 2.450 ± 0.029 ops/ns
40+
41+
Int2ObjectSyncMapBenchmark.get_only SynchronizedMap none false 50 100000 thrpt 5 0.029 ± 0.032 ops/ns
42+
Int2ObjectSyncMapBenchmark.get_only SynchronizedMap none true 50 100000 thrpt 5 0.034 ± 0.069 ops/ns
43+
Int2ObjectSyncMapBenchmark.get_only SynchronizedMap presize false 50 100000 thrpt 5 0.026 ± 0.007 ops/ns
44+
Int2ObjectSyncMapBenchmark.get_only SynchronizedMap presize true 50 100000 thrpt 5 0.036 ± 0.079 ops/ns
45+
Int2ObjectSyncMapBenchmark.get_only SynchronizedMap prepopulate false 50 100000 thrpt 5 0.023 ± 0.023 ops/ns
46+
Int2ObjectSyncMapBenchmark.get_only SynchronizedMap prepopulate true 50 100000 thrpt 5 0.019 ± 0.016 ops/ns
47+
48+
Int2ObjectSyncMapBenchmark.get_put SyncMap none false 50 100000 thrpt 5 0.621 ± 0.063 ops/ns
49+
Int2ObjectSyncMapBenchmark.get_put SyncMap none true 50 100000 thrpt 5 0.631 ± 0.078 ops/ns
50+
Int2ObjectSyncMapBenchmark.get_put SyncMap presize false 50 100000 thrpt 5 0.634 ± 0.081 ops/ns
51+
Int2ObjectSyncMapBenchmark.get_put SyncMap presize true 50 100000 thrpt 5 0.654 ± 0.157 ops/ns
52+
Int2ObjectSyncMapBenchmark.get_put SyncMap prepopulate false 50 100000 thrpt 5 0.638 ± 0.053 ops/ns
53+
Int2ObjectSyncMapBenchmark.get_put SyncMap prepopulate true 50 100000 thrpt 5 0.615 ± 0.039 ops/ns
54+
55+
Int2ObjectSyncMapBenchmark.get_put SynchronizedMap none false 50 100000 thrpt 5 0.019 ± 0.009 ops/ns
56+
Int2ObjectSyncMapBenchmark.get_put SynchronizedMap none true 50 100000 thrpt 5 0.019 ± 0.010 ops/ns
57+
Int2ObjectSyncMapBenchmark.get_put SynchronizedMap presize false 50 100000 thrpt 5 0.018 ± 0.014 ops/ns
58+
Int2ObjectSyncMapBenchmark.get_put SynchronizedMap presize true 50 100000 thrpt 5 0.019 ± 0.011 ops/ns
59+
Int2ObjectSyncMapBenchmark.get_put SynchronizedMap prepopulate false 50 100000 thrpt 5 0.020 ± 0.011 ops/ns
60+
Int2ObjectSyncMapBenchmark.get_put SynchronizedMap prepopulate true 50 100000 thrpt 5 0.015 ± 0.012 ops/ns
61+
62+
Int2ObjectSyncMapBenchmark.put_only SyncMap none false 50 100000 thrpt 5 0.223 ± 0.082 ops/ns
63+
Int2ObjectSyncMapBenchmark.put_only SyncMap none true 50 100000 thrpt 5 0.242 ± 0.035 ops/ns
64+
Int2ObjectSyncMapBenchmark.put_only SyncMap presize false 50 100000 thrpt 5 0.239 ± 0.128 ops/ns
65+
Int2ObjectSyncMapBenchmark.put_only SyncMap presize true 50 100000 thrpt 5 0.252 ± 0.115 ops/ns
66+
Int2ObjectSyncMapBenchmark.put_only SyncMap prepopulate false 50 100000 thrpt 5 0.258 ± 0.046 ops/ns
67+
Int2ObjectSyncMapBenchmark.put_only SyncMap prepopulate true 50 100000 thrpt 5 0.506 ± 0.055 ops/ns
68+
69+
Int2ObjectSyncMapBenchmark.put_only SynchronizedMap none false 50 100000 thrpt 5 0.022 ± 0.001 ops/ns
70+
Int2ObjectSyncMapBenchmark.put_only SynchronizedMap none true 50 100000 thrpt 5 0.021 ± 0.001 ops/ns
71+
Int2ObjectSyncMapBenchmark.put_only SynchronizedMap presize false 50 100000 thrpt 5 0.023 ± 0.011 ops/ns
72+
Int2ObjectSyncMapBenchmark.put_only SynchronizedMap presize true 50 100000 thrpt 5 0.026 ± 0.025 ops/ns
73+
Int2ObjectSyncMapBenchmark.put_only SynchronizedMap prepopulate false 50 100000 thrpt 5 0.022 ± 0.005 ops/ns
74+
Int2ObjectSyncMapBenchmark.put_only SynchronizedMap prepopulate true 50 100000 thrpt 5 0.021 ± 0.001 ops/ns
75+
```

collections-fastutil/readme.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<div align="center">
2+
<h1>Sync Collections `fastutil`</h1>
3+
<br/>
4+
</div>
5+
6+
<div align="center">
7+
8+
![Build Status](https://github.com/vectrix-space/sync/actions/workflows/build.yml/badge.svg)
9+
[![MIT License](https://img.shields.io/badge/license-MIT-blue)](license.txt)
10+
[![Discord](https://img.shields.io/discord/819522977586348052)](https://discord.gg/rYpaxPFQrj)
11+
[![Maven Central](https://img.shields.io/maven-central/v/space.vectrix/sync-collections?label=stable)](https://search.maven.org/search?q=g:space.vectrix%20AND%20a:sync*)
12+
![Maven Snapshot Version](https://img.shields.io/badge/dynamic/xml?url=https%3A%2F%2Fcentral.sonatype.com%2Frepository%2Fmaven-snapshots%2Fspace%2Fvectrix%2Fsync-collections%2Fmaven-metadata.xml&query=%2Fmetadata%2Fversioning%2Flatest&label=dev)
13+
14+
</div>
15+
16+
> This project is experimental and not considered stable for production yet.
17+
18+
Provides concurrent thread-safe collections for highly concurrent scenarios with `fastutil` in Java.
19+
20+
* **X2ObjectSyncMap**: A high-performance implementation of `ConcurrentMap` in Java.
21+
22+
* Fully compatible with the `fastutil` X2Object collections.
23+
* Delivers up to **40× higher read and write throughput** than `X2ObjectMaps#synchronize()` under heavy contention.
24+
25+
## Dependency
26+
27+
Sync is available at the Maven Central Repository.
28+
29+
**Gradle**
30+
31+
```kotlin
32+
dependencies {
33+
implementation("space.vectrix:sync-collections-fastutil:1.0.0-SNAPSHOT")
34+
}
35+
```
36+
37+
**Maven**
38+
39+
```xml
40+
<dependency>
41+
<groupId>space.vectrix</groupId>
42+
<artifactId>sync-collections-fastutil</artifactId>
43+
<version>1.0.0-SNAPSHOT</version>
44+
</dependency>
45+
```
46+
47+
## Performance
48+
49+
The benchmarks below were recorded on a _M4 Macbook Pro_.
50+
51+
- [SyncMap Benchmarks](benchmarks.md#syncmap)
52+
53+
## Inspiration & Credits
54+
55+
`SyncMap` draws inspiration from Go’s `sync` package — particularly its `sync.Map` and synchronization primitives — to deliver efficient, lock-free behavior in Java.
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* This file is part of sync, licensed under the MIT License.
3+
*
4+
* Copyright (c) 2025 vectrix.space
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package space.vectrix.sync.collections.fastutil;
25+
26+
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
27+
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
28+
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
29+
import java.util.SplittableRandom;
30+
import java.util.concurrent.TimeUnit;
31+
import org.openjdk.jmh.annotations.Benchmark;
32+
import org.openjdk.jmh.annotations.BenchmarkMode;
33+
import org.openjdk.jmh.annotations.Fork;
34+
import org.openjdk.jmh.annotations.Level;
35+
import org.openjdk.jmh.annotations.Measurement;
36+
import org.openjdk.jmh.annotations.Mode;
37+
import org.openjdk.jmh.annotations.OutputTimeUnit;
38+
import org.openjdk.jmh.annotations.Param;
39+
import org.openjdk.jmh.annotations.Scope;
40+
import org.openjdk.jmh.annotations.Setup;
41+
import org.openjdk.jmh.annotations.State;
42+
import org.openjdk.jmh.annotations.Threads;
43+
import org.openjdk.jmh.annotations.Warmup;
44+
import org.openjdk.jmh.infra.Blackhole;
45+
46+
@Fork(1)
47+
@Warmup(iterations = 5)
48+
@Measurement(iterations = 5)
49+
@BenchmarkMode(Mode.Throughput)
50+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
51+
public class Int2ObjectSyncMapBenchmark {
52+
@State(Scope.Benchmark)
53+
public static class Container {
54+
@Param({ "SyncMap", "SynchronizedMap" })
55+
private String implementation;
56+
57+
@Param({ "none", "presize", "prepopulate" })
58+
private String mode;
59+
60+
@Param({ "false", "true" })
61+
private boolean prime;
62+
63+
@Param({ "100000" })
64+
private int size;
65+
66+
private Int2ObjectMap<Integer> map;
67+
68+
@Setup(Level.Iteration)
69+
public void setup() {
70+
final boolean presized = !"none".equalsIgnoreCase(this.mode);
71+
final boolean prepopulate = "prepopulate".equalsIgnoreCase(this.mode);
72+
73+
switch(this.implementation) {
74+
case "SyncMap" -> this.map = presized ? new Int2ObjectSyncMap<>(Hashing.FASTEST_MIX, this.size) : new Int2ObjectSyncMap<>(Hashing.FASTEST_MIX);
75+
case "SynchronizedMap" -> this.map = presized
76+
? Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>(this.size))
77+
: Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>());
78+
default -> throw new IllegalArgumentException("Unable to identify implementation: " + this.implementation);
79+
}
80+
81+
if(prepopulate) {
82+
for(int i = 0; i < this.size; i++) {
83+
this.map.put(i, Integer.valueOf(i));
84+
}
85+
}
86+
87+
if(this.prime) {
88+
if(this.map instanceof final Int2ObjectSyncMap<Integer> syncMap) {
89+
syncMap.promote();
90+
} else {
91+
for(int i = 0; i < this.size; i++) {
92+
this.map.get(i);
93+
}
94+
}
95+
}
96+
}
97+
}
98+
99+
@State(Scope.Thread)
100+
public static class Sample {
101+
@Param({ "50" })
102+
private int readPercentage;
103+
104+
private int cursor;
105+
private int length;
106+
private boolean[] isRead;
107+
108+
@Setup(Level.Iteration)
109+
public void setup(final Container container) {
110+
this.length = container.size;
111+
this.isRead = new boolean[this.length];
112+
113+
final SplittableRandom random = new SplittableRandom(Thread.currentThread().getId());
114+
for(int i = 0; i < this.length; i++) {
115+
this.isRead[i] = random.nextInt(100) < this.readPercentage;
116+
}
117+
118+
this.cursor = 0;
119+
}
120+
121+
private int next() {
122+
final int i = this.cursor;
123+
this.cursor = (i + 1) % this.length;
124+
return i;
125+
}
126+
}
127+
128+
@Benchmark
129+
@Threads(8)
130+
public void get_only(final Container container, final Sample sample, final Blackhole blackhole) {
131+
final int key = sample.next();
132+
blackhole.consume(container.map.get(key));
133+
}
134+
135+
@Benchmark
136+
@Threads(8)
137+
public void put_only(final Container container, final Sample sample, final Blackhole blackhole) {
138+
final int key = sample.next();
139+
blackhole.consume(container.map.put(key, Integer.valueOf(key)));
140+
}
141+
142+
@Benchmark
143+
@Threads(8)
144+
public void get_put(final Container container, final Sample sample, final Blackhole blackhole) {
145+
final int key = sample.next();
146+
147+
if(sample.isRead[key]) {
148+
blackhole.consume(container.map.get(key));
149+
} else {
150+
blackhole.consume(container.map.put(key, Integer.valueOf(key)));
151+
}
152+
}
153+
}

collections-fastutil/src/main/java/module-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import org.jspecify.annotations.NullMarked;
22

33
/**
4-
* Sync Collections `fastutil`: Thread-safe collection implementations for highly concurrent scenarios in `fastutil`.
4+
* Sync Collections `fastutil`: Thread-safe collection implementations for highly concurrent scenarios with `fastutil`.
55
*/
66
@NullMarked
77
module space.vectrix.sync.collections.fastutil {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/**
2-
* Sync Collections `fastutil`: Thread-safe collection implementations for highly concurrent scenarios in `fastutil`.
2+
* Sync Collections `fastutil`: Thread-safe collection implementations for highly concurrent scenarios with `fastutil`.
33
*/
44
package space.vectrix.sync.collections.fastutil;

0 commit comments

Comments
 (0)