This is an automated email from the ASF dual-hosted git repository.
ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new ec554101ace IGNITE-25943 Add basic JMH benchmark for sorted indexes
(#6271)
ec554101ace is described below
commit ec554101ace074bb2eaaaa09b058f142eb351b6f
Author: Ivan Bessonov <[email protected]>
AuthorDate: Fri Jul 18 11:19:59 2025 +0300
IGNITE-25943 Add basic JMH benchmark for sorted indexes (#6271)
---
.../benchmark/VolatilePageMemoryBenchmarkBase.java | 90 +++++++++++
modules/storage-page-memory/build.gradle | 2 +
...istentPageMemoryProfileConfigurationSchema.java | 3 +-
...latilePageMemoryProfileConfigurationSchema.java | 3 +-
.../benchmarks/SortedIndexTreeInsertBenchmark.java | 177 +++++++++++++++++++++
5 files changed, 273 insertions(+), 2 deletions(-)
diff --git
a/modules/page-memory/src/testFixtures/java/org/apache/ignite/internal/pagememory/benchmark/VolatilePageMemoryBenchmarkBase.java
b/modules/page-memory/src/testFixtures/java/org/apache/ignite/internal/pagememory/benchmark/VolatilePageMemoryBenchmarkBase.java
new file mode 100644
index 00000000000..88ba67c9105
--- /dev/null
+++
b/modules/page-memory/src/testFixtures/java/org/apache/ignite/internal/pagememory/benchmark/VolatilePageMemoryBenchmarkBase.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.pagememory.benchmark;
+
+import static org.apache.ignite.internal.pagememory.PageIdAllocator.FLAG_AUX;
+
+import java.util.Random;
+import org.apache.ignite.internal.pagememory.PageMemory;
+import
org.apache.ignite.internal.pagememory.configuration.VolatileDataRegionConfiguration;
+import org.apache.ignite.internal.pagememory.freelist.FreeList;
+import org.apache.ignite.internal.pagememory.freelist.FreeListImpl;
+import org.apache.ignite.internal.pagememory.inmemory.VolatilePageMemory;
+import org.apache.ignite.internal.pagememory.io.PageIoRegistry;
+import org.apache.ignite.internal.pagememory.reuse.ReuseList;
+import org.apache.ignite.internal.util.Constants;
+import org.apache.ignite.internal.util.OffheapReadWriteLock;
+
+/**
+ * Base class for various benchmarks. Can start and stop a volatile data
region. Also pre-allocates a free list, because all structures need
+ * it anyway.
+ */
+public class VolatilePageMemoryBenchmarkBase {
+ /** Size of a data region. Should be large enough to fit all the data. */
+ private static final long REGION_SIZE = 4L * Constants.GiB;
+
+ /** Page size. We may want to make it configurable in the future. Let's
use a more performant 4Kb for now. */
+ protected static final int PAGE_SIZE = 4 * Constants.KiB;
+
+ /** Group ID constant for the benchmark. Could be anything. */
+ protected static final int GROUP_ID = 1;
+ /** Partition ID constant for the benchmark. Could be anything. */
+ protected static final int PARTITION_ID = 0;
+
+ /** An instance of {@link Random}. */
+ protected static final Random RANDOM = new
Random(System.currentTimeMillis());
+
+ /** A {@link PageMemory} instance. */
+ protected VolatilePageMemory volatilePageMemory;
+
+ /** A {@link FreeListImpl} instance to be used as both {@link FreeList}
and {@link ReuseList}. */
+ protected FreeListImpl freeList;
+
+ /**
+ * Starts data region and pre-allocates a free list.
+ */
+ public void setup() throws Exception {
+ var ioRegistry = new PageIoRegistry();
+ ioRegistry.loadFromServiceLoader();
+
+ volatilePageMemory = new VolatilePageMemory(
+
VolatileDataRegionConfiguration.builder().pageSize(PAGE_SIZE).initSize(REGION_SIZE).maxSize(REGION_SIZE).build(),
+ ioRegistry,
+ new
OffheapReadWriteLock(OffheapReadWriteLock.DEFAULT_CONCURRENCY_LEVEL)
+ );
+
+ volatilePageMemory.start();
+
+ freeList = new FreeListImpl(
+ "freeList",
+ GROUP_ID,
+ PARTITION_ID,
+ volatilePageMemory,
+ volatilePageMemory.allocatePageNoReuse(GROUP_ID, PARTITION_ID,
FLAG_AUX),
+ true,
+ null
+ );
+ }
+
+ /**
+ * Stops data region.
+ */
+ public void tearDown() throws Exception {
+ volatilePageMemory.stop(true);
+ }
+}
diff --git a/modules/storage-page-memory/build.gradle
b/modules/storage-page-memory/build.gradle
index 0989b08b448..c88e38e1e2f 100644
--- a/modules/storage-page-memory/build.gradle
+++ b/modules/storage-page-memory/build.gradle
@@ -39,6 +39,7 @@ dependencies {
annotationProcessor project(':ignite-configuration-annotation-processor')
annotationProcessor libs.auto.service
+ testAnnotationProcessor libs.jmh.annotation.processor
testImplementation project(':ignite-core')
testImplementation project(':ignite-storage-api')
testImplementation project(':ignite-configuration')
@@ -51,6 +52,7 @@ dependencies {
testImplementation testFixtures(project(':ignite-schema'))
testImplementation testFixtures(project(':ignite-page-memory'))
testImplementation testFixtures(project(':ignite-metrics'))
+ testImplementation libs.jmh.core
testFixturesAnnotationProcessor
project(':ignite-configuration-annotation-processor')
testFixturesAnnotationProcessor libs.auto.service
diff --git
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/configuration/schema/PersistentPageMemoryProfileConfigurationSchema.java
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/configuration/schema/PersistentPageMemoryProfileConfigurationSchema.java
index df3bdf39f0d..4b5d1558e68 100644
---
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/configuration/schema/PersistentPageMemoryProfileConfigurationSchema.java
+++
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/configuration/schema/PersistentPageMemoryProfileConfigurationSchema.java
@@ -23,11 +23,12 @@ import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.configuration.validation.OneOf;
import org.apache.ignite.internal.pagememory.configuration.ReplacementMode;
import
org.apache.ignite.internal.storage.configurations.StorageProfileConfigurationSchema;
+import
org.apache.ignite.internal.storage.pagememory.PersistentPageMemoryStorageEngine;
/**
* Persistent storage profile configuration schema.
*/
-@PolymorphicConfigInstance("aipersist")
+@PolymorphicConfigInstance(PersistentPageMemoryStorageEngine.ENGINE_NAME)
public class PersistentPageMemoryProfileConfigurationSchema extends
StorageProfileConfigurationSchema {
/**
* Random-LRU page replacement algorithm.
diff --git
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/configuration/schema/VolatilePageMemoryProfileConfigurationSchema.java
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/configuration/schema/VolatilePageMemoryProfileConfigurationSchema.java
index d2e455dfcce..cb0a1c31471 100644
---
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/configuration/schema/VolatilePageMemoryProfileConfigurationSchema.java
+++
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/configuration/schema/VolatilePageMemoryProfileConfigurationSchema.java
@@ -21,11 +21,12 @@ import
org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
import org.apache.ignite.configuration.annotation.PublicName;
import org.apache.ignite.configuration.annotation.Value;
import
org.apache.ignite.internal.storage.configurations.StorageProfileConfigurationSchema;
+import
org.apache.ignite.internal.storage.pagememory.VolatilePageMemoryStorageEngine;
/**
* In-memory storage profile configuration schema.
*/
-@PolymorphicConfigInstance("aimem")
+@PolymorphicConfigInstance(VolatilePageMemoryStorageEngine.ENGINE_NAME)
public class VolatilePageMemoryProfileConfigurationSchema extends
StorageProfileConfigurationSchema {
/**
* Initial memory region size in bytes.
diff --git
a/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/benchmarks/SortedIndexTreeInsertBenchmark.java
b/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/benchmarks/SortedIndexTreeInsertBenchmark.java
new file mode 100644
index 00000000000..0cedd3cf8c8
--- /dev/null
+++
b/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/benchmarks/SortedIndexTreeInsertBenchmark.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.storage.pagememory.benchmarks;
+
+import static org.apache.ignite.internal.pagememory.PageIdAllocator.FLAG_AUX;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import org.apache.ignite.internal.binarytuple.BinaryTupleBuilder;
+import
org.apache.ignite.internal.pagememory.benchmark.VolatilePageMemoryBenchmarkBase;
+import org.apache.ignite.internal.storage.RowId;
+import org.apache.ignite.internal.storage.index.StorageSortedIndexDescriptor;
+import
org.apache.ignite.internal.storage.index.StorageSortedIndexDescriptor.StorageSortedIndexColumnDescriptor;
+import
org.apache.ignite.internal.storage.pagememory.index.freelist.IndexColumns;
+import
org.apache.ignite.internal.storage.pagememory.index.sorted.SortedIndexRow;
+import
org.apache.ignite.internal.storage.pagememory.index.sorted.SortedIndexTree;
+import org.apache.ignite.internal.type.NativeType;
+import org.apache.ignite.internal.type.NativeTypes;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+/**
+ * A micro-benchmark for sorted index tree in a volatile data region.
+ *
+ * <p>In this benchmark the warmup must be much longer than the measurement.
This is because the insertion duration correlates with a tree
+ * height, which grows logarithmically with the number of inserted rows.
Logarithm starts to grow slowly only after a certain threshold, but
+ * before that it grows very quickly. We want to exhaust that growth before we
start measuring the performance.
+ */
+@Warmup(iterations = 20, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@Fork(1)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+public class SortedIndexTreeInsertBenchmark extends
VolatilePageMemoryBenchmarkBase {
+ /** Index ID constant for the benchmark. Could be anything. */
+ private static final int INDEX_ID = 1;
+
+ /** Some fake row ID for benchmark. Reused in all operations, because
allocating a new one every time is slow. */
+ private static final RowId ROW_ID = new RowId(PARTITION_ID);
+
+ /** Benchmark parameterization. We want to measure many different index
columns descriptors. The list will eventually be expanded. */
+ @Param({"LONG", "STRING_16"})
+ public IndexDescriptorParam columnTypes;
+
+ /** Benchmark parameter class. */
+ public enum IndexDescriptorParam {
+ /** A single column index with a long value. */
+ LONG(List.of(descriptor(0, NativeTypes.INT64)),
SortedIndexTreeInsertBenchmark::newLongTuple),
+ /** A single column index with a string value, which will have only
16-character values. */
+ STRING_16(List.of(descriptor(0, NativeTypes.STRING)),
SortedIndexTreeInsertBenchmark::newString16Tuple);
+
+ private final List<StorageSortedIndexColumnDescriptor>
columnDescriptors;
+ private final Supplier<ByteBuffer> tupleFactory;
+
+ IndexDescriptorParam(List<StorageSortedIndexColumnDescriptor>
columnDescriptors, Supplier<ByteBuffer> tupleFactory) {
+ this.columnDescriptors = columnDescriptors;
+ this.tupleFactory = tupleFactory;
+ }
+
+ /**
+ * Returns the list of column descriptors for the index.
+ */
+ List<StorageSortedIndexColumnDescriptor> columnDescriptors() {
+ return columnDescriptors;
+ }
+
+ /**
+ * Creates a new tuple the corresponds to the index descriptor.
+ */
+ ByteBuffer createNewTuple() {
+ return tupleFactory.get();
+ }
+ }
+
+ /** An instance of {@link SortedIndexTree}. */
+ private SortedIndexTree sortedIndexTree;
+
+ /**
+ * Initializes the benchmark state.
+ */
+ @Setup
+ @Override
+ public void setup() throws Exception {
+ super.setup();
+
+ sortedIndexTree = SortedIndexTree.createNew(
+ GROUP_ID,
+ "sortedIndex",
+ PARTITION_ID,
+ volatilePageMemory,
+ new AtomicLong(),
+ volatilePageMemory.allocatePageNoReuse(GROUP_ID, PARTITION_ID,
FLAG_AUX),
+ freeList,
+ new StorageSortedIndexDescriptor(INDEX_ID,
columnTypes.columnDescriptors(), false)
+ );
+ }
+
+ /**
+ * Invalidates the benchmark state.
+ */
+ @TearDown
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Checks the performance of inserting a new row into the sorted index
tree.
+ */
+ @Benchmark
+ public void putx() throws Exception {
+ ByteBuffer buffer = columnTypes.createNewTuple();
+
+ sortedIndexTree.putx(new SortedIndexRow(new IndexColumns(PARTITION_ID,
buffer), ROW_ID));
+ }
+
+ private static StorageSortedIndexColumnDescriptor descriptor(int i,
NativeType nativeType) {
+ return new StorageSortedIndexColumnDescriptor("col" + i, nativeType,
false, true, true);
+ }
+
+ private static ByteBuffer newLongTuple() {
+ long longValue = RANDOM.nextLong() | Long.MIN_VALUE;
+
+ return new BinaryTupleBuilder(1, 10,
true).appendLong(longValue).build();
+ }
+
+ private static ByteBuffer newString16Tuple() {
+ String stringValue = Long.toHexString(RANDOM.nextLong() |
Long.MIN_VALUE);
+
+ return new BinaryTupleBuilder(1, 18,
true).appendString(stringValue).build();
+ }
+
+ /**
+ * Runs the benchmark.
+ *
+ * @param args args
+ * @throws Exception if something goes wrong
+ */
+ public static void main(String[] args) throws Exception {
+ Options build = new OptionsBuilder()
+ .include(SortedIndexTreeInsertBenchmark.class.getName() +
".*").build();
+
+ new Runner(build).run();
+ }
+}