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();
+    }
+}

Reply via email to