RFR: 7903: FastAccessNumberMap is slow for sparse values
Richard Startin
duke at openjdk.org
Tue Sep 27 16:57:52 UTC 2022
This reworks `FastAccessNumberMap` so that a small bitset can be stored in each page of values, which can be used to skip over null values during iteration. A simple benchmark shows a modest enhancement to calls to `get`, but significant improvements when the map contains sparse values (iteration now scales with the number of non-null values, not the number of pages).
import org.openjdk.jmc.common.collection.FastAccessNumberMap;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import java.util.concurrent.TimeUnit;
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@State(Scope.Benchmark)
public class FastAccessNumberMapBenchmark {
private FastAccessNumberMap<String> map;
@Param({"1", "2", "3", "4", "64"})
int period;
private int size;
private long[] values;
@Setup(Level.Trial)
public void setup() {
map = new FastAccessNumberMap<>();
values = new long[5000];
for (int i = 0; i < 5000; i += period) {
map.put(i, String.valueOf(i));
values[size++] = i;
}
}
@Benchmark
public void get(Blackhole bh) {
for (int i = 0; i < size; i++) {
bh.consume(map.get(values[i]));
}
}
@Benchmark
public void iterate(Blackhole bh) {
map.forEach(bh::consume);
}
}
```
Tested with 17.0.4.1 2022-08-12 on a Mac M1
After:
Benchmark (period) Mode Cnt Score Error Units
FastAccessNumberMapBenchmark.get 1 avgt 5 17.864 ± 0.044 us/op
FastAccessNumberMapBenchmark.get 2 avgt 5 8.921 ± 0.124 us/op
FastAccessNumberMapBenchmark.get 3 avgt 5 5.916 ± 0.052 us/op
FastAccessNumberMapBenchmark.get 4 avgt 5 4.351 ± 0.011 us/op
FastAccessNumberMapBenchmark.get 64 avgt 5 0.273 ± 0.001 us/op
FastAccessNumberMapBenchmark.iterate 1 avgt 5 16.241 ± 0.581 us/op
FastAccessNumberMapBenchmark.iterate 2 avgt 5 8.768 ± 0.205 us/op
FastAccessNumberMapBenchmark.iterate 3 avgt 5 5.463 ± 0.051 us/op
FastAccessNumberMapBenchmark.iterate 4 avgt 5 4.095 ± 0.103 us/op
FastAccessNumberMapBenchmark.iterate 64 avgt 5 0.441 ± 0.002 us/op
Before:
Benchmark (period) Mode Cnt Score Error Units
FastAccessNumberMapBenchmark.get 1 avgt 5 18.937 ± 0.212 us/op
FastAccessNumberMapBenchmark.get 2 avgt 5 9.419 ± 0.042 us/op
FastAccessNumberMapBenchmark.get 3 avgt 5 6.151 ± 0.108 us/op
FastAccessNumberMapBenchmark.get 4 avgt 5 4.618 ± 0.031 us/op
FastAccessNumberMapBenchmark.get 64 avgt 5 0.286 ± 0.001 us/op
FastAccessNumberMapBenchmark.iterate 1 avgt 5 19.142 ± 0.611 us/op
FastAccessNumberMapBenchmark.iterate 2 avgt 5 18.930 ± 4.756 us/op
FastAccessNumberMapBenchmark.iterate 3 avgt 5 11.579 ± 0.082 us/op
FastAccessNumberMapBenchmark.iterate 4 avgt 5 11.650 ± 0.500 us/op
FastAccessNumberMapBenchmark.iterate 64 avgt 5 11.781 ± 0.541 us/op
We use the jmc common library in a backend process at Datadog and see bigger improvements than the microbenchmark above would suggest.
-------------
Commit messages:
- 7903: optimise FastAccessNumberMap.get and FastAccessNumberMap.iterate
Changes: https://git.openjdk.org/jmc/pull/431/files
Webrev: https://webrevs.openjdk.org/?repo=jmc&pr=431&range=00
Issue: https://bugs.openjdk.org/browse/JMC-7903
Stats: 77 lines in 1 file changed: 59 ins; 3 del; 15 mod
Patch: https://git.openjdk.org/jmc/pull/431.diff
Fetch: git fetch https://git.openjdk.org/jmc pull/431/head:pull/431
PR: https://git.openjdk.org/jmc/pull/431
More information about the jmc-dev
mailing list