Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 38 additions & 16 deletions dfu/src/main/java/org/enginehub/linbus/dfu/LinOps.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.function.BiConsumer;
Expand Down Expand Up @@ -86,7 +86,7 @@ public LinTag<?> emptyList() {

@Override
public LinTag<?> emptyMap() {
return LinCompoundTag.builder().build();
return LinCompoundTag.empty();
}

@Override
Expand All @@ -99,12 +99,18 @@ public <U> U convertTo(DynamicOps<U> outOps, LinTag<?> input) {
case LinLongTag tag -> outOps.createLong(tag.valueAsLong());
case LinFloatTag tag -> outOps.createFloat(tag.valueAsFloat());
case LinDoubleTag tag -> outOps.createDouble(tag.valueAsDouble());
case LinByteArrayTag tag -> outOps.createByteList(tag.view());
case LinStringTag tag -> outOps.createString(tag.value());
case LinListTag<?> tag -> convertList(outOps, tag);
case LinCompoundTag tag -> convertMap(outOps, tag);
case LinIntArrayTag tag -> outOps.createIntList(Arrays.stream(tag.value()));
case LinLongArrayTag tag -> outOps.createLongList(Arrays.stream(tag.value()));
case LinByteArrayTag tag -> outOps.createByteList(tag.view());
case LinIntArrayTag tag -> {
IntBuffer view = tag.view();
yield outOps.createIntList(IntStream.range(0, view.limit()).map(view::get));
}
case LinLongArrayTag tag -> {
LongBuffer view = tag.view();
yield outOps.createLongList(IntStream.range(0, view.limit()).mapToLong(view::get));
}
};
}

Expand Down Expand Up @@ -293,21 +299,27 @@ public DataResult<LinTag<?>> mergeToMap(LinTag<?> map, MapLike<LinTag<?>> values
return DataResult.error(() -> "mergeToMap called with non-map: " + map, map);
}
LinCompoundTag.Builder output = builderFrom(map);
List<LinTag<?>> missed = new ArrayList<>();
List<LinTag<?>> missed = null;
Iterator<Pair<LinTag<?>, LinTag<?>>> entries = values.entries().iterator();
try {
values.entries().forEach(entry -> {
while (entries.hasNext()) {
Pair<LinTag<?>, LinTag<?>> entry = entries.next();
LinTag<?> key = entry.getFirst();
if (key instanceof LinStringTag stringKey) {
output.put(stringKey.value(), entry.getSecond());
} else {
if (missed == null) {
missed = new ArrayList<>();
}
missed.add(key);
}
});
}
} catch (IllegalArgumentException e) {
return DataResult.error(e::getMessage);
}
if (!missed.isEmpty()) {
return DataResult.error(() -> "some keys are not strings: " + missed, output.build());
if (missed != null) {
List<LinTag<?>> missedKeys = missed;
return DataResult.error(() -> "some keys are not strings: " + missedKeys, output.build());
}
return DataResult.success(output.build());
}
Expand Down Expand Up @@ -368,10 +380,18 @@ public DataResult<Stream<LinTag<?>>> getStream(LinTag<?> input) {
IntStream.range(0, values.limit()).mapToObj(i -> LinByteTag.of(values.get(i)))
);
}
case LinIntArrayTag tag ->
DataResult.success(Arrays.stream(tag.value()).mapToObj(value -> (LinTag<?>) LinIntTag.of(value)));
case LinLongArrayTag tag ->
DataResult.success(Arrays.stream(tag.value()).mapToObj(value -> (LinTag<?>) LinLongTag.of(value)));
case LinIntArrayTag tag -> {
IntBuffer values = tag.view();
yield DataResult.success(
IntStream.range(0, values.limit()).mapToObj(i -> (LinTag<?>) LinIntTag.of(values.get(i)))
);
}
case LinLongArrayTag tag -> {
LongBuffer values = tag.view();
yield DataResult.success(
IntStream.range(0, values.limit()).mapToObj(i -> (LinTag<?>) LinLongTag.of(values.get(i)))
);
}
default -> DataResult.error(() -> "Not a list");
};
}
Expand All @@ -395,7 +415,8 @@ public LinTag<?> createByteList(ByteBuffer input) {
@Override
public DataResult<IntStream> getIntStream(LinTag<?> input) {
if (input instanceof LinIntArrayTag tag) {
return DataResult.success(Arrays.stream(tag.value()));
IntBuffer view = tag.view();
return DataResult.success(IntStream.range(0, view.limit()).map(view::get));
}
return DynamicOps.super.getIntStream(input);
}
Expand All @@ -408,7 +429,8 @@ public LinTag<?> createIntList(IntStream input) {
@Override
public DataResult<LongStream> getLongStream(LinTag<?> input) {
if (input instanceof LinLongArrayTag tag) {
return DataResult.success(Arrays.stream(tag.value()));
LongBuffer view = tag.view();
return DataResult.success(IntStream.range(0, view.limit()).mapToLong(view::get));
}
return DynamicOps.super.getLongStream(input);
}
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
javafx = "22.0.2"
tinylog = "2.7.0"
crankcase = "0.1.1"
jmh = "1.37"

[plugins]
crankcase-java = { id = "org.enginehub.crankcase.java", version.ref = "crankcase" }
Expand All @@ -10,6 +11,7 @@ crankcase-licensing = { id = "org.enginehub.crankcase.licensing", version.ref =
crankcase-publishing = { id = "org.enginehub.crankcase.publishing", version.ref = "crankcase" }
crankcase-release = { id = "org.enginehub.crankcase.release", version.ref = "crankcase" }
osdetector = { id = "com.google.osdetector", version = "1.7.3" }
jmh = { id = "me.champeau.jmh", version = "0.7.3" }

[libraries]
crankcase-checkstyle = { module = "org.enginehub.crankcase:checkstyle", version.ref = "crankcase" }
Expand Down
10 changes: 10 additions & 0 deletions tree/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id("org.enginehub.lin-bus.java-library-conventions")
alias(libs.plugins.crankcase.licensing)
alias(libs.plugins.crankcase.publishing)
alias(libs.plugins.jmh)
}

dependencies {
Expand All @@ -15,6 +16,15 @@ dependencies {
}
}

jmh {
jmhVersion = libs.versions.jmh
fork = 2
warmupIterations = 3
warmup = "1s"
iterations = 5
timeOnIteration = "1s"
}

publishing {
publications {
create<MavenPublication>("maven") {
Expand Down
27 changes: 27 additions & 0 deletions tree/src/jmh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# CompoundValueMap benchmarks

JMH benchmarks for the production `CompoundValueMap`, the open-addressed map backing
`LinCompoundTag`. The benchmarks live in the `org.enginehub.linbus.tree` package
so they can construct the package-private map directly.

`CompoundValueMapBenchmark` covers the map's hot paths across several sizes:

- `getHit` tests a successful get with a reused key (its `String.hashCode` is cached after the first lookup)
- `getMiss` tests an unsuccessful get with a reused key
- `getHitFreshKey` / `getMissFreshKey` build a new `String` per lookup so the hash is uncached, forcing the map to
rehash the key every time
- `construct` tests the time to build the copy of another map (the source's keys have cached hashes)
- `constructFreshKey` builds from a source whose keys have uncached hashes, so both maps hash every key during the
copy rather than the `LinkedHashMap` path reusing cached hashes
- `iterate` tests the time to iterate over the map's entries, which is a common operation when serializing

## Running

```sh
# CPU: all JMH benchmarks (fork/warmup/iteration counts come from the jmh { } block in build.gradle.kts)
./gradlew :tree:jmh

# CPU + allocation: add `profilers.add("gc")` to the jmh { } block so the benchmarks report
# `gc.alloc.rate.norm` (bytes allocated per op), or run the jar directly:
# ./gradlew :tree:jmhJar && java -jar tree/build/libs/tree-*-jmh.jar -prof gc
```
Loading
Loading