From 2799b3b12a41477893c8cb2f63f073bbc9856eb3 Mon Sep 17 00:00:00 2001 From: Mikhail Yevchenko Date: Sun, 5 Apr 2026 16:54:41 +0000 Subject: [PATCH] Add DataStore4J and LevelDB benchmark fixtures --- README.md | 56 +++++++------ pom.xml | 21 ++++- .../benchmarks/DataAccessBenchmark.java | 2 + .../fixtures/DataStore4jFixture.java | 78 ++++++++++++++++++ .../benchmark/fixtures/FixtureFactory.java | 2 + .../benchmark/fixtures/LevelDbFixture.java | 80 +++++++++++++++++++ 6 files changed, 212 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/benchmark/fixtures/DataStore4jFixture.java create mode 100644 src/main/java/com/benchmark/fixtures/LevelDbFixture.java diff --git a/README.md b/README.md index 8e6cbb2..2a37fe3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Only the content between the markers below is rewritten, so the rest of this REA -Last updated: 2026-04-05T15:49:01.412343645Z +Last updated: 2026-04-05T16:53:23.606653992Z ### System info @@ -31,7 +31,7 @@ Last updated: 2026-04-05T15:49:01.412343645Z - Benchmark: `com.benchmark.benchmarks.DataAccessBenchmark.readSingleUser` - Thread count: `12` -- Fixtures in this snapshot: `24` +- Fixtures in this snapshot: `28` ### Results @@ -39,34 +39,38 @@ Last updated: 2026-04-05T15:49:01.412343645Z | Rank | Fixture | Score | Error | Unit | |---:|---|---:|---:|---| -| 1 | `in-memory` | 1075.880 | 31.733 | `ops/us` | -| 2 | `chronicle-map` | 85.407 | 3.388 | `ops/us` | -| 3 | `memory-mapped-file` | 79.393 | 1.957 | `ops/us` | -| 4 | `mapdb` | 25.647 | 2.140 | `ops/us` | -| 5 | `sqlite-jdbc-memory` | 3.992 | 0.195 | `ops/us` | -| 6 | `lmdb` | 3.300 | 1.325 | `ops/us` | -| 7 | `kryo` | 2.425 | 0.168 | `ops/us` | -| 8 | `gson` | 2.159 | 0.080 | `ops/us` | -| 9 | `sqlite-jdbc` | 1.932 | 0.181 | `ops/us` | -| 10 | `sqlite-ormlite-memory` | 1.118 | 0.164 | `ops/us` | -| 11 | `sqlite-ormlite` | 0.864 | 0.049 | `ops/us` | -| 12 | `duckdb-jdbc` | 0.048 | 0.006 | `ops/us` | +| 1 | `in-memory` | 1071.748 | 51.460 | `ops/us` | +| 2 | `chronicle-map` | 84.556 | 5.034 | `ops/us` | +| 3 | `memory-mapped-file` | 78.969 | 1.996 | `ops/us` | +| 4 | `datastore4j` | 58.406 | 2.794 | `ops/us` | +| 5 | `leveldb` | 32.387 | 5.570 | `ops/us` | +| 6 | `mapdb` | 29.448 | 1.222 | `ops/us` | +| 7 | `sqlite-jdbc-memory` | 3.847 | 0.463 | `ops/us` | +| 8 | `lmdb` | 3.210 | 0.725 | `ops/us` | +| 9 | `kryo` | 2.463 | 0.130 | `ops/us` | +| 10 | `gson` | 2.198 | 0.057 | `ops/us` | +| 11 | `sqlite-jdbc` | 1.862 | 0.492 | `ops/us` | +| 12 | `sqlite-ormlite-memory` | 1.018 | 0.347 | `ops/us` | +| 13 | `sqlite-ormlite` | 0.835 | 0.279 | `ops/us` | +| 14 | `duckdb-jdbc` | 0.048 | 0.007 | `ops/us` | #### AverageTime | Rank | Fixture | Score | Error | Unit | |---:|---|---:|---:|---| -| 1 | `in-memory` | 0.011 | 0.001 | `us/op` | -| 2 | `chronicle-map` | 0.143 | 0.007 | `us/op` | -| 3 | `memory-mapped-file` | 0.154 | 0.004 | `us/op` | -| 4 | `mapdb` | 0.475 | 0.031 | `us/op` | -| 5 | `sqlite-jdbc-memory` | 3.093 | 0.154 | `us/op` | -| 6 | `lmdb` | 3.757 | 0.868 | `us/op` | -| 7 | `kryo` | 4.841 | 0.610 | `us/op` | -| 8 | `gson` | 5.561 | 0.416 | `us/op` | -| 9 | `sqlite-jdbc` | 6.192 | 0.496 | `us/op` | -| 10 | `sqlite-ormlite-memory` | 10.528 | 0.626 | `us/op` | -| 11 | `sqlite-ormlite` | 13.744 | 1.040 | `us/op` | -| 12 | `duckdb-jdbc` | 255.975 | 30.802 | `us/op` | +| 1 | `in-memory` | 0.012 | 0.001 | `us/op` | +| 2 | `chronicle-map` | 0.150 | 0.017 | `us/op` | +| 3 | `memory-mapped-file` | 0.155 | 0.008 | `us/op` | +| 4 | `datastore4j` | 0.228 | 0.016 | `us/op` | +| 5 | `leveldb` | 0.288 | 0.018 | `us/op` | +| 6 | `mapdb` | 0.481 | 0.032 | `us/op` | +| 7 | `sqlite-jdbc-memory` | 2.964 | 0.191 | `us/op` | +| 8 | `lmdb` | 3.708 | 1.615 | `us/op` | +| 9 | `kryo` | 4.834 | 0.863 | `us/op` | +| 10 | `gson` | 5.592 | 0.253 | `us/op` | +| 11 | `sqlite-jdbc` | 6.108 | 0.397 | `us/op` | +| 12 | `sqlite-ormlite-memory` | 10.541 | 0.393 | `us/op` | +| 13 | `sqlite-ormlite` | 17.532 | 7.855 | `us/op` | +| 14 | `duckdb-jdbc` | 339.471 | 181.558 | `us/op` | diff --git a/pom.xml b/pom.xml index 47f709c..b5d17b4 100644 --- a/pom.xml +++ b/pom.xml @@ -87,11 +87,30 @@ 1.5.1.0 + + + io.github.theuntamed839 + DataStore4J + 0.1.0 + + + + + org.iq80.leveldb + leveldb-api + 0.12 + + + org.iq80.leveldb + leveldb + 0.12 + + org.slf4j slf4j-nop - 2.0.17 + 2.1.0-alpha1 runtime diff --git a/src/main/java/com/benchmark/benchmarks/DataAccessBenchmark.java b/src/main/java/com/benchmark/benchmarks/DataAccessBenchmark.java index ddd5a92..1087b7b 100644 --- a/src/main/java/com/benchmark/benchmarks/DataAccessBenchmark.java +++ b/src/main/java/com/benchmark/benchmarks/DataAccessBenchmark.java @@ -20,6 +20,8 @@ public class DataAccessBenchmark { @Param({ "in-memory", "memory-mapped-file", + "datastore4j", + "leveldb", "lmdb", "mapdb", "duckdb-jdbc", diff --git a/src/main/java/com/benchmark/fixtures/DataStore4jFixture.java b/src/main/java/com/benchmark/fixtures/DataStore4jFixture.java new file mode 100644 index 0000000..2b93275 --- /dev/null +++ b/src/main/java/com/benchmark/fixtures/DataStore4jFixture.java @@ -0,0 +1,78 @@ +package com.benchmark.fixtures; + +import com.benchmark.model.User; +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import io.github.theuntamed839.datastore4j.db.DB; +import io.github.theuntamed839.datastore4j.db.DataStore4J; +import io.github.theuntamed839.datastore4j.db.DbOptions; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; + +public class DataStore4jFixture implements DataFixtures { + + private DB db; + private Kryo kryo; + private Path dbDir; + + @Override + public void setup(List users) throws Exception { + kryo = new Kryo(); + kryo.setRegistrationRequired(false); + kryo.register(User.class); + + dbDir = Files.createTempDirectory("benchmark-datastore4j-"); + DbOptions options = new DbOptions(); + db = new DataStore4J(dbDir, options); + + for (User user : users) { + db.put(toKey(user.getId()), serialize(user)); + } + } + + @Override + public User findById(long id) throws Exception { + byte[] bytes = db.get(toKey(id)); + return bytes == null ? null : deserialize(bytes); + } + + @Override + public void teardown() throws Exception { + if (db != null) { + db.close(); + } + if (dbDir != null && Files.exists(dbDir)) { + Files.walk(dbDir) + .sorted(Comparator.reverseOrder()) + .forEach(path -> path.toFile().delete()); + } + } + + private byte[] toKey(long id) { + return ByteBuffer.allocate(Long.BYTES) + .order(ByteOrder.BIG_ENDIAN) + .putLong(id) + .array(); + } + + private byte[] serialize(User user) { + Output output = new Output(256, -1); + kryo.writeObject(output, user); + byte[] bytes = output.toBytes(); + output.close(); + return bytes; + } + + private User deserialize(byte[] bytes) { + try (Input input = new Input(bytes)) { + return kryo.readObject(input, User.class); + } + } +} diff --git a/src/main/java/com/benchmark/fixtures/FixtureFactory.java b/src/main/java/com/benchmark/fixtures/FixtureFactory.java index ec20585..31ae003 100644 --- a/src/main/java/com/benchmark/fixtures/FixtureFactory.java +++ b/src/main/java/com/benchmark/fixtures/FixtureFactory.java @@ -6,6 +6,8 @@ public class FixtureFactory { return switch (type) { case "in-memory" -> new InMemoryFixture(); case "memory-mapped-file" -> new MemoryMappedFileFixture(); + case "datastore4j" -> new DataStore4jFixture(); + case "leveldb" -> new LevelDbFixture(); case "lmdb" -> new LmdbFixture(); case "mapdb" -> new MapDbFixture(); case "duckdb-jdbc" -> new DuckDbJdbcFixture(); diff --git a/src/main/java/com/benchmark/fixtures/LevelDbFixture.java b/src/main/java/com/benchmark/fixtures/LevelDbFixture.java new file mode 100644 index 0000000..0bfde65 --- /dev/null +++ b/src/main/java/com/benchmark/fixtures/LevelDbFixture.java @@ -0,0 +1,80 @@ +package com.benchmark.fixtures; + +import com.benchmark.model.User; +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.Options; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; + +import static org.iq80.leveldb.impl.Iq80DBFactory.factory; + +public class LevelDbFixture implements DataFixtures { + + private DB db; + private Kryo kryo; + private Path dbDir; + + @Override + public void setup(List users) throws IOException { + kryo = new Kryo(); + kryo.setRegistrationRequired(false); + kryo.register(User.class); + + dbDir = Files.createTempDirectory("benchmark-leveldb-"); + Options options = new Options(); + options.createIfMissing(true); + db = factory.open(dbDir.toFile(), options); + + for (User user : users) { + db.put(toKey(user.getId()), serialize(user)); + } + } + + @Override + public User findById(long id) { + byte[] bytes = db.get(toKey(id)); + return bytes == null ? null : deserialize(bytes); + } + + @Override + public void teardown() throws IOException { + if (db != null) { + db.close(); + } + if (dbDir != null && Files.exists(dbDir)) { + Files.walk(dbDir) + .sorted(Comparator.reverseOrder()) + .forEach(path -> path.toFile().delete()); + } + } + + private byte[] toKey(long id) { + return ByteBuffer.allocate(Long.BYTES) + .order(ByteOrder.BIG_ENDIAN) + .putLong(id) + .array(); + } + + private byte[] serialize(User user) { + Output output = new Output(256, -1); + kryo.writeObject(output, user); + byte[] bytes = output.toBytes(); + output.close(); + return bytes; + } + + private User deserialize(byte[] bytes) { + try (Input input = new Input(bytes)) { + return kryo.readObject(input, User.class); + } + } +}