summaryrefslogtreecommitdiffstats
path: root/src/rocksdb/java/jmh
diff options
context:
space:
mode:
Diffstat (limited to 'src/rocksdb/java/jmh')
-rw-r--r--src/rocksdb/java/jmh/LICENSE-HEADER.txt5
-rw-r--r--src/rocksdb/java/jmh/README.md24
-rw-r--r--src/rocksdb/java/jmh/pom.xml138
-rw-r--r--src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/ComparatorBenchmarks.java139
-rw-r--r--src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/GetBenchmarks.java139
-rw-r--r--src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/MultiGetBenchmarks.java232
-rw-r--r--src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/PutBenchmarks.java112
-rw-r--r--src/rocksdb/java/jmh/src/main/java/org/rocksdb/util/FileUtils.java59
-rw-r--r--src/rocksdb/java/jmh/src/main/java/org/rocksdb/util/KVUtils.java72
9 files changed, 920 insertions, 0 deletions
diff --git a/src/rocksdb/java/jmh/LICENSE-HEADER.txt b/src/rocksdb/java/jmh/LICENSE-HEADER.txt
new file mode 100644
index 000000000..365ee653b
--- /dev/null
+++ b/src/rocksdb/java/jmh/LICENSE-HEADER.txt
@@ -0,0 +1,5 @@
+Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ This source code is licensed under both the GPLv2 (found in the
+ COPYING file in the root directory) and Apache 2.0 License
+ (found in the LICENSE.Apache file in the root directory).
+
diff --git a/src/rocksdb/java/jmh/README.md b/src/rocksdb/java/jmh/README.md
new file mode 100644
index 000000000..1575ab517
--- /dev/null
+++ b/src/rocksdb/java/jmh/README.md
@@ -0,0 +1,24 @@
+# JMH Benchmarks for RocksJava
+
+These are micro-benchmarks for RocksJava functionality, using [JMH (Java Microbenchmark Harness)](https://openjdk.java.net/projects/code-tools/jmh/).
+
+## Compiling
+
+**Note**: This uses a specific build of RocksDB that is set in the `<version>` element of the `dependencies` section of the `pom.xml` file. If you are testing local changes you should build and install a SNAPSHOT version of rocksdbjni, and update the `pom.xml` of rocksdbjni-jmh file to test with this.
+
+For instance, this is how to install the OSX jar you just built for 6.26.0
+
+```bash
+$ mvn install:install-file -Dfile=./java/target/rocksdbjni-6.26.0-SNAPSHOT-osx.jar -DgroupId=org.rocksdb -DartifactId=rocksdbjni -Dversion=6.26.0-SNAPSHOT -Dpackaging=jar
+```
+
+```bash
+$ mvn package
+```
+
+## Running
+```bash
+$ java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar
+```
+
+NOTE: you can append `-help` to the command above to see all of the JMH runtime options.
diff --git a/src/rocksdb/java/jmh/pom.xml b/src/rocksdb/java/jmh/pom.xml
new file mode 100644
index 000000000..26615da86
--- /dev/null
+++ b/src/rocksdb/java/jmh/pom.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.rocksdb</groupId>
+ <artifactId>rocksdbjni-jmh</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <url>http://rocksdb.org/</url>
+
+ <name>rocksdbjni-jmh</name>
+ <description>JMH Benchmarks for RocksDB Java API</description>
+
+ <organization>
+ <name>Facebook, Inc.</name>
+ <url>https://www.facebook.com</url>
+ </organization>
+
+ <licenses>
+ <license>
+ <name>Apache License 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
+ <distribution>repo</distribution>
+ </license>
+ <license>
+ <name>GNU General Public License, version 2</name>
+ <url>http://www.gnu.org/licenses/gpl-2.0.html</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+ <scm>
+ <connection>scm:git:git://github.com/facebook/rocksdb.git</connection>
+ <developerConnection>scm:git:git@github.com:facebook/rocksdb.git</developerConnection>
+ <url>http://github.com/facebook/rocksdb/</url>
+ </scm>
+
+ <properties>
+ <project.build.source>1.7</project.build.source>
+ <project.build.target>1.7</project.build.target>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+ <jmh.version>1.22</jmh.version>
+ <uberjar.name>benchmarks</uberjar.name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.rocksdb</groupId>
+ <artifactId>rocksdbjni</artifactId>
+ <version>6.27.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-core</artifactId>
+ <version>${jmh.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-generator-annprocess</artifactId>
+ <version>${jmh.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.1</version>
+ <configuration>
+ <source>${project.build.source}</source>
+ <target>${project.build.target}</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>com.mycila</groupId>
+ <artifactId>license-maven-plugin</artifactId>
+ <version>3.0</version>
+ <inherited>true</inherited>
+ <configuration>
+ <header>LICENSE-HEADER.txt</header>
+ <failIfMissing>true</failIfMissing>
+ <aggregate>true</aggregate>
+ <strictCheck>true</strictCheck>
+ <excludes>
+ <exclude>pom.xml</exclude>
+ </excludes>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>3.2.1</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <finalName>${project.artifactId}-${project.version}-${uberjar.name}</finalName>
+ <transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <mainClass>org.openjdk.jmh.Main</mainClass>
+ </transformer>
+ </transformers>
+ <filters>
+ <filter>
+ <!--
+ Shading signed JARs will fail without this.
+ http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar
+ -->
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+
+</project> \ No newline at end of file
diff --git a/src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/ComparatorBenchmarks.java b/src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/ComparatorBenchmarks.java
new file mode 100644
index 000000000..1973b5487
--- /dev/null
+++ b/src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/ComparatorBenchmarks.java
@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ * This source code is licensed under both the GPLv2 (found in the
+ * COPYING file in the root directory) and Apache 2.0 License
+ * (found in the LICENSE.Apache file in the root directory).
+ */
+package org.rocksdb.jmh;
+
+import org.openjdk.jmh.annotations.*;
+import org.rocksdb.*;
+import org.rocksdb.util.BytewiseComparator;
+import org.rocksdb.util.FileUtils;
+import org.rocksdb.util.ReverseBytewiseComparator;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.rocksdb.util.KVUtils.ba;
+
+@State(Scope.Benchmark)
+public class ComparatorBenchmarks {
+
+ @Param({
+ "native_bytewise",
+ "native_reverse_bytewise",
+
+ "java_bytewise_non-direct_reused-64_adaptive-mutex",
+ "java_bytewise_non-direct_reused-64_non-adaptive-mutex",
+ "java_bytewise_non-direct_reused-64_thread-local",
+ "java_bytewise_direct_reused-64_adaptive-mutex",
+ "java_bytewise_direct_reused-64_non-adaptive-mutex",
+ "java_bytewise_direct_reused-64_thread-local",
+ "java_bytewise_non-direct_no-reuse",
+ "java_bytewise_direct_no-reuse",
+
+ "java_reverse_bytewise_non-direct_reused-64_adaptive-mutex",
+ "java_reverse_bytewise_non-direct_reused-64_non-adaptive-mutex",
+ "java_reverse_bytewise_non-direct_reused-64_thread-local",
+ "java_reverse_bytewise_direct_reused-64_adaptive-mutex",
+ "java_reverse_bytewise_direct_reused-64_non-adaptive-mutex",
+ "java_reverse_bytewise_direct_reused-64_thread-local",
+ "java_reverse_bytewise_non-direct_no-reuse",
+ "java_reverse_bytewise_direct_no-reuse"
+ })
+ public String comparatorName;
+
+ Path dbDir;
+ ComparatorOptions comparatorOptions;
+ AbstractComparator comparator;
+ Options options;
+ RocksDB db;
+
+ @Setup(Level.Trial)
+ public void setup() throws IOException, RocksDBException {
+ RocksDB.loadLibrary();
+
+ dbDir = Files.createTempDirectory("rocksjava-comparator-benchmarks");
+
+ options = new Options()
+ .setCreateIfMissing(true);
+
+ if ("native_bytewise".equals(comparatorName)) {
+ options.setComparator(BuiltinComparator.BYTEWISE_COMPARATOR);
+
+ } else if ("native_reverse_bytewise".equals(comparatorName)) {
+ options.setComparator(BuiltinComparator.REVERSE_BYTEWISE_COMPARATOR);
+
+ } else if (comparatorName.startsWith("java_")) {
+ comparatorOptions = new ComparatorOptions();
+
+ if (comparatorName.indexOf("non-direct") > -1) {
+ comparatorOptions.setUseDirectBuffer(false);
+ } else if (comparatorName.indexOf("direct") > -1) {
+ comparatorOptions.setUseDirectBuffer(true);
+ }
+
+ if (comparatorName.indexOf("no-reuse") > -1) {
+ comparatorOptions.setMaxReusedBufferSize(-1);
+ } else if (comparatorName.indexOf("_reused-") > -1) {
+ final int idx = comparatorName.indexOf("_reused-");
+ String s = comparatorName.substring(idx + 8);
+ s = s.substring(0, s.indexOf('_'));
+ comparatorOptions.setMaxReusedBufferSize(Integer.parseInt(s));
+ }
+
+ if (comparatorName.indexOf("non-adaptive-mutex") > -1) {
+ comparatorOptions.setReusedSynchronisationType(ReusedSynchronisationType.MUTEX);
+ } else if (comparatorName.indexOf("adaptive-mutex") > -1) {
+ comparatorOptions.setReusedSynchronisationType(ReusedSynchronisationType.ADAPTIVE_MUTEX);
+ } else if (comparatorName.indexOf("thread-local") > -1) {
+ comparatorOptions.setReusedSynchronisationType(ReusedSynchronisationType.THREAD_LOCAL);
+ }
+
+ if (comparatorName.startsWith("java_bytewise")) {
+ comparator = new BytewiseComparator(comparatorOptions);
+ } else if (comparatorName.startsWith("java_reverse_bytewise")) {
+ comparator = new ReverseBytewiseComparator(comparatorOptions);
+ }
+
+ options.setComparator(comparator);
+
+ } else {
+ throw new IllegalArgumentException("Unknown comparatorName: " + comparatorName);
+ }
+
+ db = RocksDB.open(options, dbDir.toAbsolutePath().toString());
+ }
+
+ @TearDown(Level.Trial)
+ public void cleanup() throws IOException {
+ db.close();
+ if (comparator != null) {
+ comparator.close();
+ }
+ if (comparatorOptions != null) {
+ comparatorOptions.close();
+ }
+ options.close();
+ FileUtils.delete(dbDir);
+ }
+
+ @State(Scope.Benchmark)
+ public static class Counter {
+ private final AtomicInteger count = new AtomicInteger();
+
+ public int next() {
+ return count.getAndIncrement();
+ }
+ }
+
+
+ @Benchmark
+ public void put(final Counter counter) throws RocksDBException {
+ final int i = counter.next();
+ db.put(ba("key" + i), ba("value" + i));
+ }
+}
diff --git a/src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/GetBenchmarks.java b/src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/GetBenchmarks.java
new file mode 100644
index 000000000..e34005c2f
--- /dev/null
+++ b/src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/GetBenchmarks.java
@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ * This source code is licensed under both the GPLv2 (found in the
+ * COPYING file in the root directory) and Apache 2.0 License
+ * (found in the LICENSE.Apache file in the root directory).
+ */
+package org.rocksdb.jmh;
+
+import org.openjdk.jmh.annotations.*;
+import org.rocksdb.*;
+import org.rocksdb.util.FileUtils;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.rocksdb.util.KVUtils.ba;
+
+@State(Scope.Benchmark)
+public class GetBenchmarks {
+
+ @Param({
+ "no_column_family",
+ "1_column_family",
+ "20_column_families",
+ "100_column_families"
+ })
+ String columnFamilyTestType;
+
+ @Param("100000")
+ int keyCount;
+
+ Path dbDir;
+ DBOptions options;
+ int cfs = 0; // number of column families
+ private AtomicInteger cfHandlesIdx;
+ ColumnFamilyHandle[] cfHandles;
+ RocksDB db;
+ private final AtomicInteger keyIndex = new AtomicInteger();
+
+ @Setup(Level.Trial)
+ public void setup() throws IOException, RocksDBException {
+ RocksDB.loadLibrary();
+
+ dbDir = Files.createTempDirectory("rocksjava-get-benchmarks");
+
+ options = new DBOptions()
+ .setCreateIfMissing(true)
+ .setCreateMissingColumnFamilies(true);
+
+ final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
+ cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY));
+
+ if ("1_column_family".equals(columnFamilyTestType)) {
+ cfs = 1;
+ } else if ("20_column_families".equals(columnFamilyTestType)) {
+ cfs = 20;
+ } else if ("100_column_families".equals(columnFamilyTestType)) {
+ cfs = 100;
+ }
+
+ if (cfs > 0) {
+ cfHandlesIdx = new AtomicInteger(1);
+ for (int i = 1; i <= cfs; i++) {
+ cfDescriptors.add(new ColumnFamilyDescriptor(ba("cf" + i)));
+ }
+ }
+
+ final List<ColumnFamilyHandle> cfHandlesList = new ArrayList<>(cfDescriptors.size());
+ db = RocksDB.open(options, dbDir.toAbsolutePath().toString(), cfDescriptors, cfHandlesList);
+ cfHandles = cfHandlesList.toArray(new ColumnFamilyHandle[0]);
+
+ // store initial data for retrieving via get
+ for (int i = 0; i < cfs; i++) {
+ for (int j = 0; j < keyCount; j++) {
+ db.put(cfHandles[i], ba("key" + j), ba("value" + j));
+ }
+ }
+
+ try (final FlushOptions flushOptions = new FlushOptions()
+ .setWaitForFlush(true)) {
+ db.flush(flushOptions);
+ }
+ }
+
+ @TearDown(Level.Trial)
+ public void cleanup() throws IOException {
+ for (final ColumnFamilyHandle cfHandle : cfHandles) {
+ cfHandle.close();
+ }
+ db.close();
+ options.close();
+ FileUtils.delete(dbDir);
+ }
+
+ private ColumnFamilyHandle getColumnFamily() {
+ if (cfs == 0) {
+ return cfHandles[0];
+ } else if (cfs == 1) {
+ return cfHandles[1];
+ } else {
+ int idx = cfHandlesIdx.getAndIncrement();
+ if (idx > cfs) {
+ cfHandlesIdx.set(1); // doesn't ensure a perfect distribution, but it's ok
+ idx = 0;
+ }
+ return cfHandles[idx];
+ }
+ }
+
+ /**
+ * Takes the next position in the index.
+ */
+ private int next() {
+ int idx;
+ int nextIdx;
+ while (true) {
+ idx = keyIndex.get();
+ nextIdx = idx + 1;
+ if (nextIdx >= keyCount) {
+ nextIdx = 0;
+ }
+
+ if (keyIndex.compareAndSet(idx, nextIdx)) {
+ break;
+ }
+ }
+ return idx;
+ }
+
+ @Benchmark
+ public byte[] get() throws RocksDBException {
+ final int keyIdx = next();
+ return db.get(getColumnFamily(), ba("key" + keyIdx));
+ }
+}
diff --git a/src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/MultiGetBenchmarks.java b/src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/MultiGetBenchmarks.java
new file mode 100644
index 000000000..c8c827444
--- /dev/null
+++ b/src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/MultiGetBenchmarks.java
@@ -0,0 +1,232 @@
+/**
+ * Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ * This source code is licensed under both the GPLv2 (found in the
+ * COPYING file in the root directory) and Apache 2.0 License
+ * (found in the LICENSE.Apache file in the root directory).
+ */
+package org.rocksdb.jmh;
+
+import static org.rocksdb.util.KVUtils.ba;
+import static org.rocksdb.util.KVUtils.keys;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.rocksdb.*;
+import org.rocksdb.util.FileUtils;
+
+@State(Scope.Thread)
+public class MultiGetBenchmarks {
+ @Param({
+ "no_column_family",
+ "1_column_family",
+ "20_column_families",
+ "100_column_families"
+ })
+ String columnFamilyTestType;
+
+ @Param({"10000", "25000", "100000"}) int keyCount;
+
+ @Param({
+ "10",
+ "100",
+ "1000",
+ "10000",
+ })
+ int multiGetSize;
+
+ @Param({"16", "64", "250", "1000", "4000", "16000"}) int valueSize;
+ @Param({"16"}) int keySize; // big enough
+
+ Path dbDir;
+ DBOptions options;
+ int cfs = 0; // number of column families
+ private AtomicInteger cfHandlesIdx;
+ ColumnFamilyHandle[] cfHandles;
+ RocksDB db;
+ private final AtomicInteger keyIndex = new AtomicInteger();
+
+ @Setup(Level.Trial)
+ public void setup() throws IOException, RocksDBException {
+ RocksDB.loadLibrary();
+
+ dbDir = Files.createTempDirectory("rocksjava-multiget-benchmarks");
+
+ options = new DBOptions()
+ .setCreateIfMissing(true)
+ .setCreateMissingColumnFamilies(true);
+
+ final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
+ cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY));
+
+ if ("1_column_family".equals(columnFamilyTestType)) {
+ cfs = 1;
+ } else if ("20_column_families".equals(columnFamilyTestType)) {
+ cfs = 20;
+ } else if ("100_column_families".equals(columnFamilyTestType)) {
+ cfs = 100;
+ }
+
+ if (cfs > 0) {
+ cfHandlesIdx = new AtomicInteger(1);
+ for (int i = 1; i <= cfs; i++) {
+ cfDescriptors.add(new ColumnFamilyDescriptor(ba("cf" + i)));
+ }
+ }
+
+ final List<ColumnFamilyHandle> cfHandlesList = new ArrayList<>(cfDescriptors.size());
+ db = RocksDB.open(options, dbDir.toAbsolutePath().toString(), cfDescriptors, cfHandlesList);
+ cfHandles = cfHandlesList.toArray(new ColumnFamilyHandle[0]);
+
+ // store initial data for retrieving via get
+ for (int i = 0; i < cfs; i++) {
+ for (int j = 0; j < keyCount; j++) {
+ final byte[] paddedValue = Arrays.copyOf(ba("value" + j), valueSize);
+ db.put(cfHandles[i], ba("key" + j), paddedValue);
+ }
+ }
+
+ try (final FlushOptions flushOptions = new FlushOptions()
+ .setWaitForFlush(true)) {
+ db.flush(flushOptions);
+ }
+ }
+
+ @TearDown(Level.Trial)
+ public void cleanup() throws IOException {
+ for (final ColumnFamilyHandle cfHandle : cfHandles) {
+ cfHandle.close();
+ }
+ db.close();
+ options.close();
+ FileUtils.delete(dbDir);
+ }
+
+ private ColumnFamilyHandle getColumnFamily() {
+ if (cfs == 0) {
+ return cfHandles[0];
+ } else if (cfs == 1) {
+ return cfHandles[1];
+ } else {
+ int idx = cfHandlesIdx.getAndIncrement();
+ if (idx > cfs) {
+ cfHandlesIdx.set(1); // doesn't ensure a perfect distribution, but it's ok
+ idx = 0;
+ }
+ return cfHandles[idx];
+ }
+ }
+
+ /**
+ * Reserves the next {@inc} positions in the index.
+ *
+ * @param inc the number by which to increment the index
+ * @param limit the limit for the index
+ * @return the index before {@code inc} is added
+ */
+ private int next(final int inc, final int limit) {
+ int idx;
+ int nextIdx;
+ while (true) {
+ idx = keyIndex.get();
+ nextIdx = idx + inc;
+ if (nextIdx >= limit) {
+ nextIdx = inc;
+ }
+
+ if (keyIndex.compareAndSet(idx, nextIdx)) {
+ break;
+ }
+ }
+
+ if (nextIdx >= limit) {
+ return -1;
+ } else {
+ return idx;
+ }
+ }
+
+ ByteBuffer keysBuffer;
+ ByteBuffer valuesBuffer;
+
+ List<ByteBuffer> valueBuffersList;
+ List<ByteBuffer> keyBuffersList;
+
+ @Setup
+ public void allocateSliceBuffers() {
+ keysBuffer = ByteBuffer.allocateDirect(keyCount * valueSize);
+ valuesBuffer = ByteBuffer.allocateDirect(keyCount * valueSize);
+ valueBuffersList = new ArrayList<>();
+ keyBuffersList = new ArrayList<>();
+ for (int i = 0; i < keyCount; i++) {
+ valueBuffersList.add(valuesBuffer.slice());
+ valuesBuffer.position(i * valueSize);
+ keyBuffersList.add(keysBuffer.slice());
+ keysBuffer.position(i * keySize);
+ }
+ }
+
+ @TearDown
+ public void freeSliceBuffers() {
+ valueBuffersList.clear();
+ }
+
+ @Benchmark
+ public List<byte[]> multiGet10() throws RocksDBException {
+ final int fromKeyIdx = next(multiGetSize, keyCount);
+ if (fromKeyIdx >= 0) {
+ final List<byte[]> keys = keys(fromKeyIdx, fromKeyIdx + multiGetSize);
+ final List<byte[]> valueResults = db.multiGetAsList(keys);
+ for (final byte[] result : valueResults) {
+ if (result.length != valueSize)
+ throw new RuntimeException("Test valueSize assumption wrong");
+ }
+ }
+ return new ArrayList<>();
+ }
+
+ @Benchmark
+ public List<RocksDB.MultiGetInstance> multiGetDirect10() throws RocksDBException {
+ final int fromKeyIdx = next(multiGetSize, keyCount);
+ if (fromKeyIdx >= 0) {
+ final List<ByteBuffer> keys = keys(keyBuffersList, fromKeyIdx, fromKeyIdx + multiGetSize);
+ final List<RocksDB.MultiGetInstance> results = db.multiGetByteBuffers(
+ keys, valueBuffersList.subList(fromKeyIdx, fromKeyIdx + multiGetSize));
+ for (final RocksDB.MultiGetInstance result : results) {
+ if (result.status.getCode() != Status.Code.Ok)
+ throw new RuntimeException("Test status assumption wrong");
+ if (result.valueSize != valueSize)
+ throw new RuntimeException("Test valueSize assumption wrong");
+ }
+ return results;
+ }
+ return new ArrayList<>();
+ }
+
+ public static void main(final String[] args) throws RunnerException {
+ final org.openjdk.jmh.runner.options.Options opt =
+ new OptionsBuilder()
+ .include(MultiGetBenchmarks.class.getSimpleName())
+ .forks(1)
+ .jvmArgs("-ea")
+ .warmupIterations(1)
+ .measurementIterations(2)
+ .forks(2)
+ .param("columnFamilyTestType=", "1_column_family")
+ .param("multiGetSize=", "10", "1000")
+ .param("keyCount=", "1000")
+ .output("jmh_output")
+ .build();
+
+ new Runner(opt).run();
+ }
+}
diff --git a/src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/PutBenchmarks.java b/src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/PutBenchmarks.java
new file mode 100644
index 000000000..5aae21cb9
--- /dev/null
+++ b/src/rocksdb/java/jmh/src/main/java/org/rocksdb/jmh/PutBenchmarks.java
@@ -0,0 +1,112 @@
+/**
+ * Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ * This source code is licensed under both the GPLv2 (found in the
+ * COPYING file in the root directory) and Apache 2.0 License
+ * (found in the LICENSE.Apache file in the root directory).
+ */
+package org.rocksdb.jmh;
+
+import org.openjdk.jmh.annotations.*;
+import org.rocksdb.*;
+import org.rocksdb.util.FileUtils;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.rocksdb.util.KVUtils.ba;
+
+@State(Scope.Benchmark)
+public class PutBenchmarks {
+
+ @Param({
+ "no_column_family",
+ "1_column_family",
+ "20_column_families",
+ "100_column_families"
+ })
+ String columnFamilyTestType;
+
+ Path dbDir;
+ DBOptions options;
+ int cfs = 0; // number of column families
+ private AtomicInteger cfHandlesIdx;
+ ColumnFamilyHandle[] cfHandles;
+ RocksDB db;
+
+ @Setup(Level.Trial)
+ public void setup() throws IOException, RocksDBException {
+ RocksDB.loadLibrary();
+
+ dbDir = Files.createTempDirectory("rocksjava-put-benchmarks");
+
+ options = new DBOptions()
+ .setCreateIfMissing(true)
+ .setCreateMissingColumnFamilies(true);
+
+ final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
+ cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY));
+
+ if ("1_column_family".equals(columnFamilyTestType)) {
+ cfs = 1;
+ } else if ("20_column_families".equals(columnFamilyTestType)) {
+ cfs = 20;
+ } else if ("100_column_families".equals(columnFamilyTestType)) {
+ cfs = 100;
+ }
+
+ if (cfs > 0) {
+ cfHandlesIdx = new AtomicInteger(1);
+ for (int i = 1; i <= cfs; i++) {
+ cfDescriptors.add(new ColumnFamilyDescriptor(ba("cf" + i)));
+ }
+ }
+
+ final List<ColumnFamilyHandle> cfHandlesList = new ArrayList<>(cfDescriptors.size());
+ db = RocksDB.open(options, dbDir.toAbsolutePath().toString(), cfDescriptors, cfHandlesList);
+ cfHandles = cfHandlesList.toArray(new ColumnFamilyHandle[0]);
+ }
+
+ @TearDown(Level.Trial)
+ public void cleanup() throws IOException {
+ for (final ColumnFamilyHandle cfHandle : cfHandles) {
+ cfHandle.close();
+ }
+ db.close();
+ options.close();
+ FileUtils.delete(dbDir);
+ }
+
+ private ColumnFamilyHandle getColumnFamily() {
+ if (cfs == 0) {
+ return cfHandles[0];
+ } else if (cfs == 1) {
+ return cfHandles[1];
+ } else {
+ int idx = cfHandlesIdx.getAndIncrement();
+ if (idx > cfs) {
+ cfHandlesIdx.set(1); // doesn't ensure a perfect distribution, but it's ok
+ idx = 0;
+ }
+ return cfHandles[idx];
+ }
+ }
+
+ @State(Scope.Benchmark)
+ public static class Counter {
+ private final AtomicInteger count = new AtomicInteger();
+
+ public int next() {
+ return count.getAndIncrement();
+ }
+ }
+
+ @Benchmark
+ public void put(final ComparatorBenchmarks.Counter counter) throws RocksDBException {
+ final int i = counter.next();
+ db.put(getColumnFamily(), ba("key" + i), ba("value" + i));
+ }
+}
diff --git a/src/rocksdb/java/jmh/src/main/java/org/rocksdb/util/FileUtils.java b/src/rocksdb/java/jmh/src/main/java/org/rocksdb/util/FileUtils.java
new file mode 100644
index 000000000..63744a14f
--- /dev/null
+++ b/src/rocksdb/java/jmh/src/main/java/org/rocksdb/util/FileUtils.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ * This source code is licensed under both the GPLv2 (found in the
+ * COPYING file in the root directory) and Apache 2.0 License
+ * (found in the LICENSE.Apache file in the root directory).
+ */
+package org.rocksdb.util;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+public final class FileUtils {
+ private static final SimpleFileVisitor<Path> DELETE_DIR_VISITOR = new DeleteDirVisitor();
+
+ /**
+ * Deletes a path from the filesystem
+ *
+ * If the path is a directory its contents
+ * will be recursively deleted before it itself
+ * is deleted.
+ *
+ * Note that removal of a directory is not an atomic-operation
+ * and so if an error occurs during removal, some of the directories
+ * descendants may have already been removed
+ *
+ * @param path the path to delete.
+ *
+ * @throws IOException if an error occurs whilst removing a file or directory
+ */
+ public static void delete(final Path path) throws IOException {
+ if (!Files.isDirectory(path)) {
+ Files.deleteIfExists(path);
+ } else {
+ Files.walkFileTree(path, DELETE_DIR_VISITOR);
+ }
+ }
+
+ private static class DeleteDirVisitor extends SimpleFileVisitor<Path> {
+ @Override
+ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
+ Files.deleteIfExists(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
+ if (exc != null) {
+ throw exc;
+ }
+
+ Files.deleteIfExists(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ }
+}
diff --git a/src/rocksdb/java/jmh/src/main/java/org/rocksdb/util/KVUtils.java b/src/rocksdb/java/jmh/src/main/java/org/rocksdb/util/KVUtils.java
new file mode 100644
index 000000000..5077291c8
--- /dev/null
+++ b/src/rocksdb/java/jmh/src/main/java/org/rocksdb/util/KVUtils.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
+ * This source code is licensed under both the GPLv2 (found in the
+ * COPYING file in the root directory) and Apache 2.0 License
+ * (found in the LICENSE.Apache file in the root directory).
+ */
+package org.rocksdb.util;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class KVUtils {
+
+ /**
+ * Get a byte array from a string.
+ *
+ * Assumes UTF-8 encoding
+ *
+ * @param string the string
+ *
+ * @return the bytes.
+ */
+ public static byte[] ba(final String string) {
+ return string.getBytes(UTF_8);
+ }
+
+ /**
+ * Get a string from a byte array.
+ *
+ * Assumes UTF-8 encoding
+ *
+ * @param bytes the bytes
+ *
+ * @return the string.
+ */
+ public static String str(final byte[] bytes) {
+ return new String(bytes, UTF_8);
+ }
+
+ /**
+ * Get a list of keys where the keys are named key1..key1+N
+ * in the range of {@code from} to {@code to} i.e. keyFrom..keyTo.
+ *
+ * @param from the first key
+ * @param to the last key
+ *
+ * @return the array of keys
+ */
+ public static List<byte[]> keys(final int from, final int to) {
+ final List<byte[]> keys = new ArrayList<>(to - from);
+ for (int i = from; i < to; i++) {
+ keys.add(ba("key" + i));
+ }
+ return keys;
+ }
+
+ public static List<ByteBuffer> keys(
+ final List<ByteBuffer> keyBuffers, final int from, final int to) {
+ final List<ByteBuffer> keys = new ArrayList<>(to - from);
+ for (int i = from; i < to; i++) {
+ final ByteBuffer key = keyBuffers.get(i);
+ key.clear();
+ key.put(ba("key" + i));
+ key.flip();
+ keys.add(key);
+ }
+ return keys;
+ }
+}