Add new benchmark fixtures and runner improvements
This commit is contained in:
@@ -59,6 +59,13 @@
|
|||||||
<version>6.1</version>
|
<version>6.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Chronicle Map -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.openhft</groupId>
|
||||||
|
<artifactId>chronicle-map</artifactId>
|
||||||
|
<version>2026.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Silence SLF4J "no binding" warning -->
|
<!-- Silence SLF4J "no binding" warning -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
@@ -101,7 +108,7 @@
|
|||||||
<finalName>benchmarks</finalName>
|
<finalName>benchmarks</finalName>
|
||||||
<transformers>
|
<transformers>
|
||||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
<mainClass>org.openjdk.jmh.Main</mainClass>
|
<mainClass>com.benchmark.runner.BenchmarkMain</mainClass>
|
||||||
</transformer>
|
</transformer>
|
||||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||||
</transformers>
|
</transformers>
|
||||||
|
|||||||
@@ -17,7 +17,16 @@ import java.util.concurrent.TimeUnit;
|
|||||||
@Threads(Threads.MAX)
|
@Threads(Threads.MAX)
|
||||||
public class DataAccessBenchmark {
|
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 String fixtureType;
|
||||||
|
|
||||||
private DataFixtures fixture;
|
private DataFixtures fixture;
|
||||||
|
|||||||
@@ -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<Long, byte[]> usersById;
|
||||||
|
private Kryo kryo;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup(List<User> users) throws IOException {
|
||||||
|
kryo = new Kryo();
|
||||||
|
kryo.setRegistrationRequired(false);
|
||||||
|
kryo.register(User.class);
|
||||||
|
|
||||||
|
Map<Long, byte[]> 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<Long, byte[]> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,13 @@ public class FixtureFactory {
|
|||||||
|
|
||||||
public static DataFixtures create(String type) {
|
public static DataFixtures create(String type) {
|
||||||
return switch (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 "gson" -> new GsonFileFixture();
|
||||||
case "kryo" -> new KryoFileFixture();
|
case "kryo" -> new KryoFileFixture();
|
||||||
|
case "sqlite-jdbc-memory" -> new SqliteInMemoryJdbcFixture();
|
||||||
|
case "sqlite-ormlite-memory" -> new SqliteInMemoryOrmLiteFixture();
|
||||||
case "sqlite-jdbc" -> new SqliteJdbcFixture();
|
case "sqlite-jdbc" -> new SqliteJdbcFixture();
|
||||||
case "sqlite-ormlite" -> new SqliteOrmLiteFixture();
|
case "sqlite-ormlite" -> new SqliteOrmLiteFixture();
|
||||||
default -> throw new IllegalArgumentException("Unknown fixture type: " + type);
|
default -> throw new IllegalArgumentException("Unknown fixture type: " + type);
|
||||||
|
|||||||
@@ -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<Long, User> usersById;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup(List<User> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<User> users) throws IOException {
|
||||||
|
kryo = new Kryo();
|
||||||
|
kryo.setRegistrationRequired(false);
|
||||||
|
kryo.register(User.class);
|
||||||
|
|
||||||
|
Map<Long, byte[]> 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<Long, byte[]> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<User> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<User, Long> dao;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup(List<User> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,65 @@
|
|||||||
package com.benchmark.runner;
|
package com.benchmark.runner;
|
||||||
|
|
||||||
import com.benchmark.benchmarks.DataAccessBenchmark;
|
import com.benchmark.benchmarks.DataAccessBenchmark;
|
||||||
|
import org.openjdk.jmh.runner.options.CommandLineOptions;
|
||||||
import org.openjdk.jmh.runner.Runner;
|
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.Options;
|
||||||
import org.openjdk.jmh.runner.options.OptionsBuilder;
|
import org.openjdk.jmh.runner.options.OptionsBuilder;
|
||||||
|
|
||||||
public class BenchmarkMain {
|
public class BenchmarkMain {
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
Options opt = new OptionsBuilder()
|
if (isJmhListingOrHelpCommand(args)) {
|
||||||
.include(DataAccessBenchmark.class.getSimpleName())
|
org.openjdk.jmh.Main.main(args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandLineOptions commandLineOptions = new CommandLineOptions(args);
|
||||||
|
|
||||||
|
ChainedOptionsBuilder builder = new OptionsBuilder()
|
||||||
|
.parent(commandLineOptions)
|
||||||
.jvmArgsPrepend(
|
.jvmArgsPrepend(
|
||||||
"--enable-native-access=ALL-UNNAMED",
|
"--enable-native-access=ALL-UNNAMED",
|
||||||
"--sun-misc-unsafe-memory-access=allow"
|
"--sun-misc-unsafe-memory-access=allow",
|
||||||
)
|
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||||
.build();
|
"--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();
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user