diff options
Diffstat (limited to 'src/rocksdb/java/jmh')
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; + } +} |