diff --git a/pom.xml b/pom.xml
index 3ece027..20146cd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,6 +59,13 @@
6.1
+
+
+ net.openhft
+ chronicle-map
+ 2026.1
+
+
org.slf4j
@@ -101,7 +108,7 @@
benchmarks
- org.openjdk.jmh.Main
+ com.benchmark.runner.BenchmarkMain
diff --git a/src/main/java/com/benchmark/benchmarks/DataAccessBenchmark.java b/src/main/java/com/benchmark/benchmarks/DataAccessBenchmark.java
index 4c64924..d8a8299 100644
--- a/src/main/java/com/benchmark/benchmarks/DataAccessBenchmark.java
+++ b/src/main/java/com/benchmark/benchmarks/DataAccessBenchmark.java
@@ -17,7 +17,16 @@ import java.util.concurrent.TimeUnit;
@Threads(Threads.MAX)
public class DataAccessBenchmark {
- @Param({"gson", "kryo", "sqlite-jdbc", "sqlite-ormlite"})
+ @Param({
+ "in-memory",
+ "memory-mapped-file",
+ "gson",
+ "kryo",
+ "sqlite-jdbc-memory",
+ "sqlite-ormlite-memory",
+ "sqlite-jdbc",
+ "sqlite-ormlite"
+ })
private String fixtureType;
private DataFixtures fixture;
diff --git a/src/main/java/com/benchmark/fixtures/ChronicleMapFixture.java b/src/main/java/com/benchmark/fixtures/ChronicleMapFixture.java
new file mode 100644
index 0000000..24d9dd4
--- /dev/null
+++ b/src/main/java/com/benchmark/fixtures/ChronicleMapFixture.java
@@ -0,0 +1,72 @@
+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 net.openhft.chronicle.map.ChronicleMap;
+import net.openhft.chronicle.map.ChronicleMapBuilder;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ChronicleMapFixture implements DataFixtures {
+
+ private ChronicleMap usersById;
+ private Kryo kryo;
+
+ @Override
+ public void setup(List users) throws IOException {
+ kryo = new Kryo();
+ kryo.setRegistrationRequired(false);
+ kryo.register(User.class);
+
+ Map encodedUsers = new HashMap<>(users.size());
+ int maxValueSize = 1;
+ for (User user : users) {
+ byte[] encoded = serialize(user);
+ encodedUsers.put(user.getId(), encoded);
+ maxValueSize = Math.max(maxValueSize, encoded.length);
+ }
+
+ usersById = ChronicleMapBuilder
+ .of(Long.class, byte[].class)
+ .name("users-by-id")
+ .entries(users.size())
+ .averageValue(new byte[maxValueSize])
+ .create();
+
+ for (Map.Entry entry : encodedUsers.entrySet()) {
+ usersById.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public User findById(long id) {
+ byte[] encoded = usersById.get(id);
+ return encoded == null ? null : deserialize(encoded);
+ }
+
+ @Override
+ public void teardown() throws IOException {
+ if (usersById != null) {
+ usersById.close();
+ }
+ }
+
+ 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 d66569d..8e1c681 100644
--- a/src/main/java/com/benchmark/fixtures/FixtureFactory.java
+++ b/src/main/java/com/benchmark/fixtures/FixtureFactory.java
@@ -4,8 +4,13 @@ public class FixtureFactory {
public static DataFixtures create(String type) {
return switch (type) {
+ case "in-memory" -> new InMemoryFixture();
+ case "memory-mapped-file" -> new MemoryMappedFileFixture();
+ case "chronicle-map" -> new ChronicleMapFixture();
case "gson" -> new GsonFileFixture();
case "kryo" -> new KryoFileFixture();
+ case "sqlite-jdbc-memory" -> new SqliteInMemoryJdbcFixture();
+ case "sqlite-ormlite-memory" -> new SqliteInMemoryOrmLiteFixture();
case "sqlite-jdbc" -> new SqliteJdbcFixture();
case "sqlite-ormlite" -> new SqliteOrmLiteFixture();
default -> throw new IllegalArgumentException("Unknown fixture type: " + type);
diff --git a/src/main/java/com/benchmark/fixtures/InMemoryFixture.java b/src/main/java/com/benchmark/fixtures/InMemoryFixture.java
new file mode 100644
index 0000000..083de8c
--- /dev/null
+++ b/src/main/java/com/benchmark/fixtures/InMemoryFixture.java
@@ -0,0 +1,32 @@
+package com.benchmark.fixtures;
+
+import com.benchmark.model.User;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class InMemoryFixture implements DataFixtures {
+
+ private Map usersById;
+
+ @Override
+ public void setup(List users) {
+ usersById = new HashMap<>((int) ((users.size() / 0.75f) + 1));
+ for (User user : users) {
+ usersById.put(user.getId(), user);
+ }
+ }
+
+ @Override
+ public User findById(long id) {
+ return usersById.get(id);
+ }
+
+ @Override
+ public void teardown() {
+ if (usersById != null) {
+ usersById.clear();
+ }
+ }
+}
diff --git a/src/main/java/com/benchmark/fixtures/MemoryMappedFileFixture.java b/src/main/java/com/benchmark/fixtures/MemoryMappedFileFixture.java
new file mode 100644
index 0000000..f42d7d4
--- /dev/null
+++ b/src/main/java/com/benchmark/fixtures/MemoryMappedFileFixture.java
@@ -0,0 +1,98 @@
+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 java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class MemoryMappedFileFixture implements DataFixtures {
+
+ private Kryo kryo;
+ private Path dataFile;
+ private RandomAccessFile randomAccessFile;
+ private FileChannel fileChannel;
+ private MappedByteBuffer mappedBuffer;
+ private int recordSize;
+
+ @Override
+ public void setup(List users) throws IOException {
+ kryo = new Kryo();
+ kryo.setRegistrationRequired(false);
+ kryo.register(User.class);
+
+ Map encodedUsers = new HashMap<>(users.size());
+ int maxValueSize = 1;
+ for (User user : users) {
+ byte[] encoded = serialize(user);
+ encodedUsers.put(user.getId(), encoded);
+ maxValueSize = Math.max(maxValueSize, encoded.length);
+ }
+
+ recordSize = Integer.BYTES + maxValueSize;
+ long fileSize = (long) recordSize * users.size();
+
+ dataFile = Files.createTempFile("benchmark-mapped-", ".bin");
+ randomAccessFile = new RandomAccessFile(dataFile.toFile(), "rw");
+ randomAccessFile.setLength(fileSize);
+ fileChannel = randomAccessFile.getChannel();
+ mappedBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
+
+ for (Map.Entry entry : encodedUsers.entrySet()) {
+ int offset = offsetFor(entry.getKey());
+ mappedBuffer.position(offset);
+ mappedBuffer.putInt(entry.getValue().length);
+ mappedBuffer.put(entry.getValue());
+ }
+ }
+
+ @Override
+ public User findById(long id) {
+ MappedByteBuffer duplicate = mappedBuffer.duplicate();
+ duplicate.position(offsetFor(id));
+ int length = duplicate.getInt();
+ byte[] bytes = new byte[length];
+ duplicate.get(bytes);
+ return deserialize(bytes);
+ }
+
+ @Override
+ public void teardown() throws IOException {
+ if (fileChannel != null) {
+ fileChannel.close();
+ }
+ if (randomAccessFile != null) {
+ randomAccessFile.close();
+ }
+ if (dataFile != null) {
+ Files.deleteIfExists(dataFile);
+ }
+ }
+
+ private int offsetFor(long id) {
+ return (int) ((id - 1) * (long) recordSize);
+ }
+
+ 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/SqliteInMemoryJdbcFixture.java b/src/main/java/com/benchmark/fixtures/SqliteInMemoryJdbcFixture.java
new file mode 100644
index 0000000..acaf3c2
--- /dev/null
+++ b/src/main/java/com/benchmark/fixtures/SqliteInMemoryJdbcFixture.java
@@ -0,0 +1,62 @@
+package com.benchmark.fixtures;
+
+import com.benchmark.model.User;
+import com.benchmark.util.DbUtil;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+
+public class SqliteInMemoryJdbcFixture implements DataFixtures {
+
+ private Connection connection;
+ private PreparedStatement selectStmt;
+
+ @Override
+ public void setup(List users) throws Exception {
+ String dbUrl = "jdbc:sqlite:file:benchmark-jdbc-memory-" + UUID.randomUUID() + "?mode=memory&cache=shared";
+ connection = DriverManager.getConnection(dbUrl);
+ DbUtil.createSchema(connection);
+
+ connection.setAutoCommit(false);
+ try (PreparedStatement ps = connection.prepareStatement(
+ "INSERT INTO users(id, name, email, age, created_at) VALUES(?,?,?,?,?)")) {
+ for (User user : users) {
+ ps.setLong(1, user.getId());
+ ps.setString(2, user.getName());
+ ps.setString(3, user.getEmail());
+ ps.setInt(4, user.getAge());
+ ps.setLong(5, user.getCreatedAt());
+ ps.addBatch();
+ }
+ ps.executeBatch();
+ }
+ connection.commit();
+ connection.setAutoCommit(true);
+
+ selectStmt = connection.prepareStatement(
+ "SELECT id, name, email, age, created_at FROM users WHERE id = ?");
+ }
+
+ @Override
+ public User findById(long id) throws SQLException {
+ selectStmt.setLong(1, id);
+ try (ResultSet rs = selectStmt.executeQuery()) {
+ return rs.next() ? DbUtil.mapRow(rs) : null;
+ }
+ }
+
+ @Override
+ public void teardown() throws Exception {
+ if (selectStmt != null) {
+ selectStmt.close();
+ }
+ if (connection != null) {
+ connection.close();
+ }
+ }
+}
diff --git a/src/main/java/com/benchmark/fixtures/SqliteInMemoryOrmLiteFixture.java b/src/main/java/com/benchmark/fixtures/SqliteInMemoryOrmLiteFixture.java
new file mode 100644
index 0000000..062a20d
--- /dev/null
+++ b/src/main/java/com/benchmark/fixtures/SqliteInMemoryOrmLiteFixture.java
@@ -0,0 +1,43 @@
+package com.benchmark.fixtures;
+
+import com.benchmark.model.User;
+import com.j256.ormlite.dao.Dao;
+import com.j256.ormlite.dao.DaoManager;
+import com.j256.ormlite.jdbc.JdbcConnectionSource;
+import com.j256.ormlite.table.TableUtils;
+
+import java.util.List;
+import java.util.UUID;
+
+public class SqliteInMemoryOrmLiteFixture implements DataFixtures {
+
+ private JdbcConnectionSource connectionSource;
+ private Dao dao;
+
+ @Override
+ public void setup(List users) throws Exception {
+ String dbUrl = "jdbc:sqlite:file:benchmark-ormlite-memory-" + UUID.randomUUID() + "?mode=memory&cache=shared";
+ connectionSource = new JdbcConnectionSource(dbUrl);
+ TableUtils.createTableIfNotExists(connectionSource, User.class);
+ dao = DaoManager.createDao(connectionSource, User.class);
+
+ dao.callBatchTasks(() -> {
+ for (User user : users) {
+ dao.create(user);
+ }
+ return null;
+ });
+ }
+
+ @Override
+ public User findById(long id) throws Exception {
+ return dao.queryForId(id);
+ }
+
+ @Override
+ public void teardown() throws Exception {
+ if (connectionSource != null) {
+ connectionSource.close();
+ }
+ }
+}
diff --git a/src/main/java/com/benchmark/runner/BenchmarkMain.java b/src/main/java/com/benchmark/runner/BenchmarkMain.java
index 796e195..5ee4a64 100644
--- a/src/main/java/com/benchmark/runner/BenchmarkMain.java
+++ b/src/main/java/com/benchmark/runner/BenchmarkMain.java
@@ -1,20 +1,65 @@
package com.benchmark.runner;
import com.benchmark.benchmarks.DataAccessBenchmark;
+import org.openjdk.jmh.runner.options.CommandLineOptions;
import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
public class BenchmarkMain {
public static void main(String[] args) throws Exception {
- Options opt = new OptionsBuilder()
- .include(DataAccessBenchmark.class.getSimpleName())
- .jvmArgsPrepend(
- "--enable-native-access=ALL-UNNAMED",
- "--sun-misc-unsafe-memory-access=allow"
- )
- .build();
+ if (isJmhListingOrHelpCommand(args)) {
+ org.openjdk.jmh.Main.main(args);
+ return;
+ }
+
+ CommandLineOptions commandLineOptions = new CommandLineOptions(args);
+
+ ChainedOptionsBuilder builder = new OptionsBuilder()
+ .parent(commandLineOptions)
+ .jvmArgsPrepend(
+ "--enable-native-access=ALL-UNNAMED",
+ "--sun-misc-unsafe-memory-access=allow",
+ "--add-opens=java.base/java.lang=ALL-UNNAMED",
+ "--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
+ "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+ "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
+ "--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
+ "--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
+ "--add-opens=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+ "--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
+ "--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
+ "--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+ "--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"
+ );
+
+ if (args.length == 0) {
+ builder.include(DataAccessBenchmark.class.getSimpleName());
+ }
+
+ Options opt = builder.build();
new Runner(opt).run();
}
+
+ private static boolean isJmhListingOrHelpCommand(String[] args) {
+ for (String arg : args) {
+ if ("-l".equals(arg) || "-lp".equals(arg) || "-lprof".equals(arg)
+ || "-h".equals(arg) || "--help".equals(arg)) {
+ return true;
+ }
+ }
+ return false;
+ }
}