From 9dcc4f67bc7a99cd049a52f5027b1380dbfac366 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:42:02 +0000 Subject: [PATCH 01/32] Apply X axis label --- jmhplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmhplot.py b/jmhplot.py index bf4089a..27d874e 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -265,7 +265,7 @@ def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, p plt.title( f'{str(indexKeys)}={str(indexTuple)} include={include_benchmarks} exclude={exclude_benchmarks}') - plt.xlabel("X") + plt.xlabel("# Operations") plt.ylabel("t (ns)") plt.legend(loc='lower right') plt.grid(b='True', which='both') From a676dfa586136fd8d34512d6efa905751390b493 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:48:07 +0000 Subject: [PATCH 02/32] Filter for interesting lines more consistently --- jmhplot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jmhplot.py b/jmhplot.py index 27d874e..a380da1 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -116,8 +116,8 @@ def normalize_data_frame_from_path(path: pathlib.Path): except pd.errors.EmptyDataError: break - # every 9th line is the interesting one, discard the rest - df = df.iloc[::9, :] + # df = df.iloc[::9, :] + df = df[~df['Benchmark'].str.contains(':')] df["Benchmark"] = df["Benchmark"].apply(lambda x: x.split('.')[-1]) if normalized is None: normalized = df From ec0e6184ebdbf829964804e65b09172f5fdc0af8 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 19 Dec 2025 11:13:12 +0000 Subject: [PATCH 03/32] Allow for more than 10 colors in the resulting graphs --- jmhplot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jmhplot.py b/jmhplot.py index a380da1..5a933d9 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -257,6 +257,9 @@ def plot_result_axis_bars(ax, resultSet: ResultSet) -> None: def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, path: pathlib.Path, include_benchmarks: str, exclude_benchmarks: str, label: str): + # Add this line to use a 20-color palette + plt.rc('axes', prop_cycle=plt.cycler('color', plt.cm.berlin.colors)) + fig = plt.figure(num=None, figsize=(18, 12), dpi=80, facecolor='w', edgecolor='k') ax = plt.subplot() From 7c8096a7c5a868b1be72f5b929faaf8b8a7d484b Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 19 Dec 2025 11:22:09 +0000 Subject: [PATCH 04/32] Allow for more than 10 colors in the resulting graphs --- jmhplot.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/jmhplot.py b/jmhplot.py index 5a933d9..c1bc149 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -257,8 +257,15 @@ def plot_result_axis_bars(ax, resultSet: ResultSet) -> None: def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, path: pathlib.Path, include_benchmarks: str, exclude_benchmarks: str, label: str): - # Add this line to use a 20-color palette - plt.rc('axes', prop_cycle=plt.cycler('color', plt.cm.berlin.colors)) + # Determine how many colors we need + num_benchmarks = len(resultSet) + + # Sample gist_ncar (or nipy_spectral) at discrete intervals + cmap = plt.get_cmap('gist_ncar') + colors = [cmap(i / num_benchmarks) for i in range(num_benchmarks)] + + # Set the property cycle with these colors + plt.rc('axes', prop_cycle=plt.cycler('color', colors)) fig = plt.figure(num=None, figsize=(18, 12), dpi=80, facecolor='w', edgecolor='k') From 436b1b24ee1dd9695858c5f301832bc6c208f692 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Mon, 29 Dec 2025 14:07:58 +0000 Subject: [PATCH 05/32] Plot with HW specs and legend outside graph. --- jmhplot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jmhplot.py b/jmhplot.py index c1bc149..4207457 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -273,11 +273,12 @@ def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, p plot_result_axis_bars(ax, resultSet) + plt.suptitle("x86_64 - Xeon E5-1650 v3 @ 3.50GHz - 128GB ECC RAM - Ubuntu 24.04.3 LTS") plt.title( f'{str(indexKeys)}={str(indexTuple)} include={include_benchmarks} exclude={exclude_benchmarks}') plt.xlabel("# Operations") plt.ylabel("t (ns)") - plt.legend(loc='lower right') + plt.legend(loc='upper left', bbox_to_anchor=(1, 1)) plt.grid(b='True', which='both') name = f'fig_{"_".join([str(t) for t in indexTuple])}_{label}.png' From ac65e0dd235bdd81bdd3c5f706074d89e9407df1 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Mon, 29 Dec 2025 14:13:42 +0000 Subject: [PATCH 06/32] Ensure legend isn't cropped. --- jmhplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmhplot.py b/jmhplot.py index 4207457..e518e66 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -285,7 +285,7 @@ def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, p if path.is_file(): path = path.parent() - fig.savefig(path.joinpath(name)) + fig.savefig(path.joinpath(name), bbox_inches='tight') alpha_pattern = re.compile(f'[A-Za-z0-9_\-+]') From b8d829e94040556054b325c32a7e913b2b2b5efd Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Mon, 29 Dec 2025 14:28:13 +0000 Subject: [PATCH 07/32] Add Kernel information to title --- jmhplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmhplot.py b/jmhplot.py index e518e66..f505d29 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -273,7 +273,7 @@ def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, p plot_result_axis_bars(ax, resultSet) - plt.suptitle("x86_64 - Xeon E5-1650 v3 @ 3.50GHz - 128GB ECC RAM - Ubuntu 24.04.3 LTS") + plt.suptitle("x86_64 - Xeon E5-1650 v3 @ 3.50GHz - 128GB ECC RAM - Ubuntu 24.04.3 LTS - Kernel: 6.14.0-36-generic") plt.title( f'{str(indexKeys)}={str(indexTuple)} include={include_benchmarks} exclude={exclude_benchmarks}') plt.xlabel("# Operations") From e384eda2299b4ed550c424f24344312f91336bea Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Tue, 30 Dec 2025 09:35:31 +0000 Subject: [PATCH 08/32] Matplotlib has renamed the parameter 'b' to 'visible'. --- jmhplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmhplot.py b/jmhplot.py index f505d29..853fbd4 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -279,7 +279,7 @@ def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, p plt.xlabel("# Operations") plt.ylabel("t (ns)") plt.legend(loc='upper left', bbox_to_anchor=(1, 1)) - plt.grid(b='True', which='both') + plt.grid(visible='True', which='both') name = f'fig_{"_".join([str(t) for t in indexTuple])}_{label}.png' From 6d2fccfa85bee34ea8c611904394d6829f61514a Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 20:36:35 +0000 Subject: [PATCH 09/32] POC for Get with FFP --- src/main/c++/getputjni/GetPutJNI.cpp | 7 + .../jnibench/jmhbench/GetJNIBenchmark.java | 655 ++++++++++-------- .../jmhbench/cache/MemorySegmentCache.java | 62 ++ 3 files changed, 441 insertions(+), 283 deletions(-) create mode 100644 src/main/java/com/evolvedbinary/jnibench/jmhbench/cache/MemorySegmentCache.java diff --git a/src/main/c++/getputjni/GetPutJNI.cpp b/src/main/c++/getputjni/GetPutJNI.cpp index 810e74e..62ff32f 100644 --- a/src/main/c++/getputjni/GetPutJNI.cpp +++ b/src/main/c++/getputjni/GetPutJNI.cpp @@ -377,6 +377,13 @@ jint Java_com_evolvedbinary_jnibench_common_getputjni_GetPutJNI_getIntoIndirectB return get_size; } +jint getIntoRawPointer(const char* key, char* dest, int dest_len) { + std::string value = GetByteArrayInternal(key); + int size = std::min((int)value.size(), dest_len); + memcpy(dest, value.c_str(), size); + return size; +} + /* * Class: com_evolvedbinary_jnibench_common_getputjni_GetPutJNI * Method: putFromIndirectByteBufferGetRegion diff --git a/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java b/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java index 7887ecb..8ceef4b 100644 --- a/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java +++ b/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java @@ -1,18 +1,18 @@ /** * Copyright © 2021, Evolved Binary Ltd * All rights reserved. - * + *

* Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + *

* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -26,24 +26,45 @@ */ package com.evolvedbinary.jnibench.jmhbench; +import static java.lang.foreign.ValueLayout.ADDRESS; +import static java.lang.foreign.ValueLayout.JAVA_INT; + import com.evolvedbinary.jnibench.common.getputjni.GetPutJNI; import com.evolvedbinary.jnibench.consbench.NarSystem; -import com.evolvedbinary.jnibench.jmhbench.cache.*; -import com.evolvedbinary.jnibench.jmhbench.common.*; -import io.netty.buffer.PooledByteBufAllocator; -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.infra.Blackhole; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; +import com.evolvedbinary.jnibench.jmhbench.cache.AllocationCache; +import com.evolvedbinary.jnibench.jmhbench.cache.ByteArrayCache; +import com.evolvedbinary.jnibench.jmhbench.cache.DirectByteBufferCache; +import com.evolvedbinary.jnibench.jmhbench.cache.IndirectByteBufferCache; +import com.evolvedbinary.jnibench.jmhbench.cache.MemorySegmentCache; +import com.evolvedbinary.jnibench.jmhbench.cache.NettyByteBufCache; +import com.evolvedbinary.jnibench.jmhbench.cache.UnsafeBufferCache; +import com.evolvedbinary.jnibench.jmhbench.common.JMHCaller; import io.netty.buffer.ByteBuf; - +import io.netty.buffer.PooledByteBufAllocator; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemorySegment; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +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.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; /** * Benchmark getting byte arrays from native methods. @@ -56,288 +77,356 @@ //@Measurement(iterations = 500, time = 2000, timeUnit = TimeUnit.NANOSECONDS) public class GetJNIBenchmark { - private static final Logger LOG = Logger.getLogger(GetJNIBenchmark.class.getName()); + private static final Logger LOG = Logger.getLogger(GetJNIBenchmark.class.getName()); - static { - NarSystem.loadLibrary(); - } - - @State(Scope.Benchmark) - public static class GetJNIBenchmarkState { + static { + NarSystem.loadLibrary(); + } - @Param({ - "10", - "50", - "128", - "512", - "1024", - "4096", - "8192", - "16384", - "32768", - "65536", - "131072"}) - int valueSize; + @State(Scope.Benchmark) + public static class GetJNIBenchmarkState { - @Param({"4", "16"}) int cacheMB; - final static int MB = 1024 * 1024; - @Param({"1024"}) int cacheEntryOverhead; + @Param({ + "10", + "50", + "128", + "512", + "1024", + "4096", + "8192", + "16384", + "32768", + "65536", + "131072"}) + int valueSize; - @Param({"none", "copyout", "bytesum", "longsum"}) String checksum; - AllocationCache.Checksum readChecksum; + @Param({"4", "16"}) + int cacheMB; + final static int MB = 1024 * 1024; + @Param({"1024"}) + int cacheEntryOverhead; - String keyBase; - byte[] keyBytes; + @Param({"none", "copyout", "bytesum", "longsum"}) + String checksum; + AllocationCache.Checksum readChecksum; - JMHCaller caller; + String keyBase; + byte[] keyBytes; + private MemorySegment keyMemorySegment; - @Setup - public void setup() { - this.caller = JMHCaller.fromStack(); + JMHCaller caller; - keyBase = "testKeyWithReturnValueSize" + String.format("%07d", valueSize) + "Bytes"; + @Setup + public void setup() { + this.caller = JMHCaller.fromStack(); - keyBytes = keyBase.getBytes(); + keyBase = "testKeyWithReturnValueSize" + String.format("%07d", valueSize) + "Bytes"; - readChecksum = AllocationCache.Checksum.valueOf(checksum); - } + keyBytes = keyBase.getBytes(); + keyMemorySegment = MemorySegment.ofArray(keyBytes); - @TearDown - public void tearDown() { - - } + readChecksum = AllocationCache.Checksum.valueOf(checksum); } - @State(Scope.Thread) - public static class GetJNIThreadState { - - private DirectByteBufferCache directByteBufferCache = new DirectByteBufferCache(); - private UnsafeBufferCache unsafeBufferCache = new UnsafeBufferCache(); - private ByteArrayCache byteArrayCache = new ByteArrayCache(); - private IndirectByteBufferCache indirectByteBufferCache = new IndirectByteBufferCache(); - private PooledByteBufAllocator pooledByteBufAllocator; - private NettyByteBufCache nettyByteBufCache = new NettyByteBufCache(); - - int valueSize; - int cacheSize; - - @Setup - public void setup(GetJNIBenchmarkState benchmarkState, Blackhole blackhole) { - valueSize = benchmarkState.valueSize; - cacheSize = benchmarkState.cacheMB * GetJNIBenchmarkState.MB; - - switch (benchmarkState.caller.benchmarkMethod) { - case "getIntoPooledNettyByteBuf": - pooledByteBufAllocator = PooledByteBufAllocator.DEFAULT; - //create a 0-sized cache so that we can use it to do checksum - nettyByteBufCache.setup(valueSize, 0/*cacheSize*/, benchmarkState.cacheEntryOverhead, benchmarkState.readChecksum, blackhole); - break; - case "getIntoNettyByteBuf": - nettyByteBufCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, benchmarkState.readChecksum, blackhole); - break; - case "getIntoDirectByteBuffer": - directByteBufferCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, benchmarkState.readChecksum, blackhole); - break; - case "getIntoIndirectByteBufferSetRegion": - case "getIntoIndirectByteBufferGetElements": - case "getIntoIndirectByteBufferGetCritical": - indirectByteBufferCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, benchmarkState.readChecksum, blackhole); - break; - case "getIntoDirectByteBufferFromUnsafe": - case "buffersOnlyDirectByteBufferFromUnsafe": - case "getIntoUnsafe": - unsafeBufferCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, benchmarkState.readChecksum, blackhole); - break; - case "getIntoByteArraySetRegion": - case "getIntoByteArrayGetElements": - case "getIntoByteArrayCritical": - byteArrayCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, benchmarkState.readChecksum, blackhole); - break; - default: - throw new RuntimeException("Don't know how to setup() for benchmark: " + benchmarkState.caller.benchmarkMethod); - } - } - - @TearDown - public void tearDown(GetJNIBenchmarkState benchmarkState) { - - switch (benchmarkState.caller.benchmarkMethod) { - case "getIntoPooledNettyByteBuf": - pooledByteBufAllocator = null; - break; - case "getIntoNettyByteBuf": - nettyByteBufCache.tearDown(); - break; - case "getIntoDirectByteBuffer": - directByteBufferCache.tearDown(); - break; - case "getIntoIndirectByteBufferSetRegion": - case "getIntoIndirectByteBufferGetElements": - case "getIntoIndirectByteBufferGetCritical": - indirectByteBufferCache.tearDown(); - break; - case "getIntoDirectByteBufferFromUnsafe": - case "buffersOnlyDirectByteBufferFromUnsafe": - case "getIntoUnsafe": - unsafeBufferCache.tearDown(); - break; - case "getIntoByteArraySetRegion": - case "getIntoByteArrayGetElements": - case "getIntoByteArrayCritical": - byteArrayCache.tearDown(); - break; - default: - throw new RuntimeException("Don't know how to tearDown() for benchmark: " + benchmarkState.caller.benchmarkMethod); - } - } - } + @TearDown + public void tearDown() { - //@Benchmark - public void buffersOnlyDirectByteBufferFromUnsafe(GetJNIThreadState threadState) { - UnsafeBufferCache.UnsafeBuffer unsafeBuffer = threadState.unsafeBufferCache.acquire(); - threadState.unsafeBufferCache.release(unsafeBuffer); } - - @Benchmark - public void getIntoDirectByteBuffer(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { - ByteBuffer byteBuffer = threadState.directByteBufferCache.acquire(); - byteBuffer.clear(); - GetPutJNI.getIntoDirectByteBuffer(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, byteBuffer, benchmarkState.valueSize); - threadState.directByteBufferCache.checksumBuffer(byteBuffer); - threadState.directByteBufferCache.release(byteBuffer); + } + + @State(Scope.Thread) + public static class GetJNIThreadState { + + private DirectByteBufferCache directByteBufferCache = new DirectByteBufferCache(); + private UnsafeBufferCache unsafeBufferCache = new UnsafeBufferCache(); + private ByteArrayCache byteArrayCache = new ByteArrayCache(); + private IndirectByteBufferCache indirectByteBufferCache = new IndirectByteBufferCache(); + private PooledByteBufAllocator pooledByteBufAllocator; + private NettyByteBufCache nettyByteBufCache = new NettyByteBufCache(); + private MemorySegmentCache memorySegmentCache = new MemorySegmentCache(); + + int valueSize; + int cacheSize; + + @Setup + public void setup(GetJNIBenchmarkState benchmarkState, Blackhole blackhole) { + valueSize = benchmarkState.valueSize; + cacheSize = benchmarkState.cacheMB * GetJNIBenchmarkState.MB; + + switch (benchmarkState.caller.benchmarkMethod) { + case "getIntoPooledNettyByteBuf": + pooledByteBufAllocator = PooledByteBufAllocator.DEFAULT; + //create a 0-sized cache so that we can use it to do checksum + nettyByteBufCache.setup(valueSize, 0/*cacheSize*/, benchmarkState.cacheEntryOverhead, + benchmarkState.readChecksum, blackhole); + break; + case "getIntoNettyByteBuf": + nettyByteBufCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, benchmarkState.readChecksum, + blackhole); + break; + case "getIntoDirectByteBuffer": + directByteBufferCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, + benchmarkState.readChecksum, blackhole); + break; + case "getIntoIndirectByteBufferSetRegion": + case "getIntoIndirectByteBufferGetElements": + case "getIntoIndirectByteBufferGetCritical": + indirectByteBufferCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, + benchmarkState.readChecksum, blackhole); + break; + case "getIntoDirectByteBufferFromUnsafe": + case "buffersOnlyDirectByteBufferFromUnsafe": + case "getIntoUnsafe": + unsafeBufferCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, benchmarkState.readChecksum, + blackhole); + break; + case "getIntoByteArraySetRegion": + case "getIntoByteArrayGetElements": + case "getIntoByteArrayCritical": + byteArrayCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, benchmarkState.readChecksum, + blackhole); + break; + case "getIntoMemorySegment": + memorySegmentCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, , + benchmarkState.readChecksum, blackhole); + break; + default: + throw new RuntimeException( + "Don't know how to setup() for benchmark: " + benchmarkState.caller.benchmarkMethod); + } } - @Benchmark - public void getIntoUnsafe(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { - UnsafeBufferCache.UnsafeBuffer unsafeBuffer = threadState.unsafeBufferCache.acquire(); - int size = GetPutJNI.getIntoUnsafe(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, unsafeBuffer.handle, benchmarkState.valueSize); - threadState.unsafeBufferCache.checksumBuffer(unsafeBuffer); - threadState.unsafeBufferCache.release(unsafeBuffer); + @TearDown + public void tearDown(GetJNIBenchmarkState benchmarkState) { + + switch (benchmarkState.caller.benchmarkMethod) { + case "getIntoPooledNettyByteBuf": + pooledByteBufAllocator = null; + break; + case "getIntoNettyByteBuf": + nettyByteBufCache.tearDown(); + break; + case "getIntoDirectByteBuffer": + directByteBufferCache.tearDown(); + break; + case "getIntoIndirectByteBufferSetRegion": + case "getIntoIndirectByteBufferGetElements": + case "getIntoIndirectByteBufferGetCritical": + indirectByteBufferCache.tearDown(); + break; + case "getIntoDirectByteBufferFromUnsafe": + case "buffersOnlyDirectByteBufferFromUnsafe": + case "getIntoUnsafe": + unsafeBufferCache.tearDown(); + break; + case "getIntoByteArraySetRegion": + case "getIntoByteArrayGetElements": + case "getIntoByteArrayCritical": + byteArrayCache.tearDown(); + break; + case "getIntoMemorySegment": + memorySegmentCache.tearDown(); + break; + default: + throw new RuntimeException( + "Don't know how to tearDown() for benchmark: " + benchmarkState.caller.benchmarkMethod); + } } - - @Benchmark - public void getIntoPooledNettyByteBuf(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { - ByteBuf byteBuf = threadState.pooledByteBufAllocator.directBuffer(benchmarkState.valueSize); - byteBuf.readerIndex(0); - int size = GetPutJNI.getIntoUnsafe(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, byteBuf.memoryAddress(), benchmarkState.valueSize); - byteBuf.writerIndex(size); - //Use 0-sized cache which we created specially to do checksumBuffer operation - threadState.nettyByteBufCache.checksumBuffer(byteBuf); - // Allocated buffer already has retain count of 1 - byteBuf.release(); + } + + //@Benchmark + public void buffersOnlyDirectByteBufferFromUnsafe(GetJNIThreadState threadState) { + UnsafeBufferCache.UnsafeBuffer unsafeBuffer = threadState.unsafeBufferCache.acquire(); + threadState.unsafeBufferCache.release(unsafeBuffer); + } + + @Benchmark + public void getIntoDirectByteBuffer(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, + Blackhole blackhole) { + ByteBuffer byteBuffer = threadState.directByteBufferCache.acquire(); + byteBuffer.clear(); + GetPutJNI.getIntoDirectByteBuffer(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, byteBuffer, + benchmarkState.valueSize); + threadState.directByteBufferCache.checksumBuffer(byteBuffer); + threadState.directByteBufferCache.release(byteBuffer); + } + + @Benchmark + public void getIntoUnsafe(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { + UnsafeBufferCache.UnsafeBuffer unsafeBuffer = threadState.unsafeBufferCache.acquire(); + int size = GetPutJNI.getIntoUnsafe(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, unsafeBuffer.handle, + benchmarkState.valueSize); + threadState.unsafeBufferCache.checksumBuffer(unsafeBuffer); + threadState.unsafeBufferCache.release(unsafeBuffer); + } + + @Benchmark + public void getIntoMemorySegment(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, + Blackhole blackhole) { + final var segment = threadState.memorySegmentCache.acquire(); + final var linker = Linker.nativeLinker(); + final var symbol = linker.defaultLookup() + .find("getIntoMemorySegment") + .orElseThrow(); + final var methodHandle = linker.downcallHandle(symbol, FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS, JAVA_INT)); + + // Call via Panama MethodHandle + try { + methodHandle.invokeExact( + benchmarkState.keyMemorySegment, // Pre-allocated segment for key + segment, + benchmarkState.valueSize + ); + } catch (Throwable e) { + throw new RuntimeException(e); } - @Benchmark - public void getIntoNettyByteBuf(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { - ByteBuf byteBuf = threadState.nettyByteBufCache.acquire(); - byteBuf.readerIndex(0); - int size = GetPutJNI.getIntoUnsafe(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, byteBuf.memoryAddress(), benchmarkState.valueSize); - byteBuf.writerIndex(size); - threadState.nettyByteBufCache.checksumBuffer(byteBuf); - threadState.nettyByteBufCache.release(byteBuf); - } - - @Benchmark - public void getIntoByteArraySetRegion(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { - byte[] array = threadState.byteArrayCache.acquire(); - int size = GetPutJNI.getIntoByteArraySetRegion(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, array, benchmarkState.valueSize); - threadState.byteArrayCache.checksumBuffer(array); - threadState.byteArrayCache.release(array); - } - - @Benchmark - public void getIntoByteArrayGetElements(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { - byte[] array = threadState.byteArrayCache.acquire(); - int size = GetPutJNI.getIntoByteArrayGetElements(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, array, benchmarkState.valueSize); - threadState.byteArrayCache.checksumBuffer(array); - threadState.byteArrayCache.release(array); - } - - @Benchmark - public void getIntoByteArrayCritical(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { - byte[] array = threadState.byteArrayCache.acquire(); - int size = GetPutJNI.getIntoByteArrayCritical(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, array, benchmarkState.valueSize); - threadState.byteArrayCache.checksumBuffer(array); - threadState.byteArrayCache.release(array); - } - - //final supplied buffer(s) - //TODO this can be done in as many different ways as supplying a byte[] - //But why shouldn't we just expect the same performance as byte[] ? - //Start with one instance (one that seems good in the byte[] case), and check for surprises... - @Benchmark - public void getIntoIndirectByteBufferSetRegion(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { - ByteBuffer byteBuffer = threadState.indirectByteBufferCache.acquire(); - byteBuffer.clear(); - GetPutJNI.getIntoIndirectByteBufferSetRegion(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, byteBuffer, benchmarkState.valueSize); - threadState.indirectByteBufferCache.checksumBuffer(byteBuffer); - threadState.indirectByteBufferCache.release(byteBuffer); - } - - @Benchmark - public void getIntoIndirectByteBufferGetElements(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { - ByteBuffer byteBuffer = threadState.indirectByteBufferCache.acquire(); - byteBuffer.clear(); - int size = GetPutJNI.getIntoIndirectByteBufferGetElements(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, byteBuffer, benchmarkState.valueSize); - threadState.indirectByteBufferCache.checksumBuffer(byteBuffer); - threadState.indirectByteBufferCache.release(byteBuffer); - } - - @Benchmark - public void getIntoIndirectByteBufferGetCritical(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { - ByteBuffer byteBuffer = threadState.indirectByteBufferCache.acquire(); - byteBuffer.clear(); - int size = GetPutJNI.getIntoIndirectByteBufferGetCritical(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, byteBuffer, benchmarkState.valueSize); - threadState.indirectByteBufferCache.checksumBuffer(byteBuffer); - threadState.indirectByteBufferCache.release(byteBuffer); - } - - //create/allocate the result buffers, analogous to the "into" methods (but no unsafe ones here) - //TODO getReturnDirectByteBuffer - //TODO getReturnIndirectByteBuffer - //TODO getReturnByteArrayCritical - //TODO getReturnByteArrayGetElements - //TODO getReturnByteArraySetRegion - - //TODO env->NewDirectByteBuffer() - what aree the ownership rules ? - //TODO track whether the byte[] copying/sharing methods we are using are doing copies - //env->GetByteArrayElements(..., &is_copy) - - //TODO graphing - dig into the Python stuff a bit more - - /** - * Run from the IDE - * - * You will need this in the VM args of the run configuration, - * in order for NAR to find at runtime the native lib it has built: - * - * -Djava.library.path=PATH_TO_REPO/target/jni-benchmarks-1.0.0-SNAPSHOT-application/jni-benchmarks-1.0.0-SNAPSHOT/lib - * - * The parameters we set here configure for debugging, - * typically we want a much shorter runs than is needed for accurate benchmarking - * SO DON'T TRUST THE NUMBERS GENERATED BY THIS RUN - * fork(0) runs everything is in a single process so we don't need to configure JDWP - * Again this affects JMH - * {@link https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_12_Forking.java} - * It's a convenience for debugging the tests so that they actually run, that is all. - * - * @param args - * @throws RunnerException - */ - public static void main(String[] args) throws RunnerException { - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy.MM.dd_HH:mm:ss.SSS"); - Options opt = new OptionsBuilder() - .forks(0) - .param("checksum", "none", "copyout") - .param("valueSize", "50", "4096", "16384", "65536") - .param("cacheMB", "4") - .warmupIterations(10) - .measurementIterations(50) - .include(GetJNIBenchmark.class.getSimpleName()) - .result("analysis/testplots/" + simpleDateFormat.format(new Date()) + "_" + GetJNIBenchmark.class.getSimpleName() + ".csv") - .build(); - - new Runner(opt).run(); - } + threadState.memorySegmentCache.checksumBuffer(segment); + threadState.memorySegmentCache.release(segment); + } + + @Benchmark + public void getIntoPooledNettyByteBuf(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, + Blackhole blackhole) { + ByteBuf byteBuf = threadState.pooledByteBufAllocator.directBuffer(benchmarkState.valueSize); + byteBuf.readerIndex(0); + int size = GetPutJNI.getIntoUnsafe(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, + byteBuf.memoryAddress(), benchmarkState.valueSize); + byteBuf.writerIndex(size); + //Use 0-sized cache which we created specially to do checksumBuffer operation + threadState.nettyByteBufCache.checksumBuffer(byteBuf); + // Allocated buffer already has retain count of 1 + byteBuf.release(); + } + + @Benchmark + public void getIntoNettyByteBuf(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, + Blackhole blackhole) { + ByteBuf byteBuf = threadState.nettyByteBufCache.acquire(); + byteBuf.readerIndex(0); + int size = GetPutJNI.getIntoUnsafe(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, + byteBuf.memoryAddress(), benchmarkState.valueSize); + byteBuf.writerIndex(size); + threadState.nettyByteBufCache.checksumBuffer(byteBuf); + threadState.nettyByteBufCache.release(byteBuf); + } + + @Benchmark + public void getIntoByteArraySetRegion(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, + Blackhole blackhole) { + byte[] array = threadState.byteArrayCache.acquire(); + int size = GetPutJNI.getIntoByteArraySetRegion(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, array, + benchmarkState.valueSize); + threadState.byteArrayCache.checksumBuffer(array); + threadState.byteArrayCache.release(array); + } + + @Benchmark + public void getIntoByteArrayGetElements(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, + Blackhole blackhole) { + byte[] array = threadState.byteArrayCache.acquire(); + int size = GetPutJNI.getIntoByteArrayGetElements(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, array, + benchmarkState.valueSize); + threadState.byteArrayCache.checksumBuffer(array); + threadState.byteArrayCache.release(array); + } + + @Benchmark + public void getIntoByteArrayCritical(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, + Blackhole blackhole) { + byte[] array = threadState.byteArrayCache.acquire(); + int size = GetPutJNI.getIntoByteArrayCritical(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, array, + benchmarkState.valueSize); + threadState.byteArrayCache.checksumBuffer(array); + threadState.byteArrayCache.release(array); + } + + //final supplied buffer(s) + //TODO this can be done in as many different ways as supplying a byte[] + //But why shouldn't we just expect the same performance as byte[] ? + //Start with one instance (one that seems good in the byte[] case), and check for surprises... + @Benchmark + public void getIntoIndirectByteBufferSetRegion(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, + Blackhole blackhole) { + ByteBuffer byteBuffer = threadState.indirectByteBufferCache.acquire(); + byteBuffer.clear(); + GetPutJNI.getIntoIndirectByteBufferSetRegion(benchmarkState.keyBytes, 0, benchmarkState.keyBytes.length, byteBuffer, + benchmarkState.valueSize); + threadState.indirectByteBufferCache.checksumBuffer(byteBuffer); + threadState.indirectByteBufferCache.release(byteBuffer); + } + + @Benchmark + public void getIntoIndirectByteBufferGetElements(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, + Blackhole blackhole) { + ByteBuffer byteBuffer = threadState.indirectByteBufferCache.acquire(); + byteBuffer.clear(); + int size = GetPutJNI.getIntoIndirectByteBufferGetElements(benchmarkState.keyBytes, 0, + benchmarkState.keyBytes.length, byteBuffer, + benchmarkState.valueSize); + threadState.indirectByteBufferCache.checksumBuffer(byteBuffer); + threadState.indirectByteBufferCache.release(byteBuffer); + } + + @Benchmark + public void getIntoIndirectByteBufferGetCritical(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, + Blackhole blackhole) { + ByteBuffer byteBuffer = threadState.indirectByteBufferCache.acquire(); + byteBuffer.clear(); + int size = GetPutJNI.getIntoIndirectByteBufferGetCritical(benchmarkState.keyBytes, 0, + benchmarkState.keyBytes.length, byteBuffer, + benchmarkState.valueSize); + threadState.indirectByteBufferCache.checksumBuffer(byteBuffer); + threadState.indirectByteBufferCache.release(byteBuffer); + } + + //create/allocate the result buffers, analogous to the "into" methods (but no unsafe ones here) + //TODO getReturnDirectByteBuffer + //TODO getReturnIndirectByteBuffer + //TODO getReturnByteArrayCritical + //TODO getReturnByteArrayGetElements + //TODO getReturnByteArraySetRegion + + //TODO env->NewDirectByteBuffer() - what aree the ownership rules ? + //TODO track whether the byte[] copying/sharing methods we are using are doing copies + //env->GetByteArrayElements(..., &is_copy) + + //TODO graphing - dig into the Python stuff a bit more + + /** + * Run from the IDE + *

+ * You will need this in the VM args of the run configuration, + * in order for NAR to find at runtime the native lib it has built: + *

+ * -Djava.library.path=PATH_TO_REPO/target/jni-benchmarks-1.0.0-SNAPSHOT-application/jni-benchmarks-1.0.0-SNAPSHOT/lib + *

+ * The parameters we set here configure for debugging, + * typically we want a much shorter runs than is needed for accurate benchmarking + * SO DON'T TRUST THE NUMBERS GENERATED BY THIS RUN + * fork(0) runs everything is in a single process so we don't need to configure JDWP + * Again this affects JMH + * {@link https://github.com/openjdk/jmh/blob/master/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_12_Forking.java} + * It's a convenience for debugging the tests so that they actually run, that is all. + * + * @param args + * @throws RunnerException + */ + public static void main(String[] args) throws RunnerException { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy.MM.dd_HH:mm:ss.SSS"); + Options opt = new OptionsBuilder() + .forks(0) + .param("checksum", "none", "copyout") + .param("valueSize", "50", "4096", "16384", "65536") + .param("cacheMB", "4") + .warmupIterations(10) + .measurementIterations(50) + .include(GetJNIBenchmark.class.getSimpleName()) + .result("analysis/testplots/" + simpleDateFormat.format( + new Date()) + "_" + GetJNIBenchmark.class.getSimpleName() + ".csv") + .build(); + + new Runner(opt).run(); + } } diff --git a/src/main/java/com/evolvedbinary/jnibench/jmhbench/cache/MemorySegmentCache.java b/src/main/java/com/evolvedbinary/jnibench/jmhbench/cache/MemorySegmentCache.java new file mode 100644 index 0000000..9c6079f --- /dev/null +++ b/src/main/java/com/evolvedbinary/jnibench/jmhbench/cache/MemorySegmentCache.java @@ -0,0 +1,62 @@ +package com.evolvedbinary.jnibench.jmhbench.cache; + +import static java.lang.foreign.ValueLayout.JAVA_BYTE; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.stream.IntStream; + +public class MemorySegmentCache extends LinkedListAllocationCache { + private final Arena arena; + + public MemorySegmentCache() { + arena = Arena.ofShared(); + } + + + @Override + MemorySegment allocate(final int valueSize) { + return arena.allocate(valueSize); + } + + @Override + void free(final MemorySegment buffer) { + // Nothing to do here, as we override taerdown() directly. + } + + @Override + public void tearDown() { + super.tearDown(); + arena.close(); + } + + @Override + protected int byteChecksum(final MemorySegment item) { + return IntStream.range(0, (int) item.byteSize()).map(offset -> item.get(JAVA_BYTE, offset)).sum(); + } + + @Override + protected int longChecksum(final MemorySegment item) { + return byteChecksum(item); + } + + @Override + protected byte[] copyOut(final MemorySegment item) { + // Get a cached byte array of the correct size + byte[] array = byteArrayOfSize((int) item.byteSize()); + + // Perform bulk copy from native memory to Java heap array + MemorySegment.copy(item, ValueLayout.JAVA_BYTE, 0, array, 0, (int) item.byteSize()); + + return array; + } + + @Override + protected long copyIn(final MemorySegment item, final byte fillByte) { + // Highly optimized bulk fill (native memset equivalent) + item.fill(fillByte); + + return fillByte; + } +} From f8bb1575a32ecabfc18a33fe10e14dad996039f0 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 20:47:11 +0000 Subject: [PATCH 10/32] POC for Get with FFP --- .../com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java b/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java index 8ceef4b..fe0e2a4 100644 --- a/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java +++ b/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java @@ -187,8 +187,8 @@ public void setup(GetJNIBenchmarkState benchmarkState, Blackhole blackhole) { blackhole); break; case "getIntoMemorySegment": - memorySegmentCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, , - benchmarkState.readChecksum, blackhole); + memorySegmentCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, benchmarkState.readChecksum, + blackhole); break; default: throw new RuntimeException( From 5ae29a9843bf312398f25852d3a0860795006a50 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 20:51:00 +0000 Subject: [PATCH 11/32] POC for Get with FFP --- pom.xml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f67ad03..6a4b3d5 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 com.evolvedbinary.jni @@ -195,7 +196,8 @@ ${project.artifactId}-${project.version}-${uberjar.name} - + org.openjdk.jmh.Main @@ -277,6 +279,18 @@ 21 21 + + + + maven-compiler-plugin + + + --enable-preview + + + + + java25 From 1fbe4fdbf516251a87e3af8b07dfb7ca73f91cb4 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 20:51:53 +0000 Subject: [PATCH 12/32] POC for Get with FFP --- pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pom.xml b/pom.xml index 6a4b3d5..b07a44b 100644 --- a/pom.xml +++ b/pom.xml @@ -285,6 +285,9 @@ maven-compiler-plugin + -proc:full + -h + ${project.build.directory}/nar/javah-include --enable-preview From 7519228fb8934c832c471d7e6d6b6e35ae6c96e3 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 20:56:23 +0000 Subject: [PATCH 13/32] POC for Get with FFP --- jmh_tiny_get_java21.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 jmh_tiny_get_java21.json diff --git a/jmh_tiny_get_java21.json b/jmh_tiny_get_java21.json new file mode 100644 index 0000000..7cb3e12 --- /dev/null +++ b/jmh_tiny_get_java21.json @@ -0,0 +1,19 @@ +\{ + "benchmark": "GetJNIBenchmark", + "jvmargs": ["Xmx4G", "XX:ErrorFile=./results/hs_err_pid%p.log", "XX:+HeapDumpOnOutOfMemoryError", "--enable-preview"], + "params": { + "valueSize": [50, 1024, 4096, 16384], + "cacheMB": [1], + "checksum": ["none", "copyout"] + }, + "options": { + "batchsize": 1, + "warmupiterations": 5, + "warmuptime": "10ms", + "iterations": 5, + "time": "50ms" + }, + "result.path": "./results", + "java.library.path": "target/jni-benchmarks-1.0.1-SNAPSHOT-application/jni-benchmarks-1.0.1-SNAPSHOT/lib", + "jar": "target/jni-benchmarks-1.0.1-SNAPSHOT-benchmarks.nar" +} From a351ff8feff9be2114abfec1be60b169d7059c64 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 20:57:07 +0000 Subject: [PATCH 14/32] POC for Get with FFP --- jmh_tiny_get_java21.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmh_tiny_get_java21.json b/jmh_tiny_get_java21.json index 7cb3e12..8462f7f 100644 --- a/jmh_tiny_get_java21.json +++ b/jmh_tiny_get_java21.json @@ -1,4 +1,4 @@ -\{ +{ "benchmark": "GetJNIBenchmark", "jvmargs": ["Xmx4G", "XX:ErrorFile=./results/hs_err_pid%p.log", "XX:+HeapDumpOnOutOfMemoryError", "--enable-preview"], "params": { From e2ba86b176de49378a5114a76b15da6b83efe5df Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 20:57:47 +0000 Subject: [PATCH 15/32] POC for Get with FFP --- jmh_tiny_get_java21.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmh_tiny_get_java21.json b/jmh_tiny_get_java21.json index 8462f7f..9d3b78e 100644 --- a/jmh_tiny_get_java21.json +++ b/jmh_tiny_get_java21.json @@ -1,6 +1,6 @@ { "benchmark": "GetJNIBenchmark", - "jvmargs": ["Xmx4G", "XX:ErrorFile=./results/hs_err_pid%p.log", "XX:+HeapDumpOnOutOfMemoryError", "--enable-preview"], + "jvmargs": ["Xmx4G", "XX:ErrorFile=./results/hs_err_pid%p.log", "XX:+HeapDumpOnOutOfMemoryError", "-enable-preview"], "params": { "valueSize": [50, 1024, 4096, 16384], "cacheMB": [1], From ae271de77496d535a9458065c487ec53e45d0559 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 21:12:23 +0000 Subject: [PATCH 16/32] POC for Get with FFP --- src/main/c++/getputjni/GetPutJNI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/c++/getputjni/GetPutJNI.cpp b/src/main/c++/getputjni/GetPutJNI.cpp index 62ff32f..c235454 100644 --- a/src/main/c++/getputjni/GetPutJNI.cpp +++ b/src/main/c++/getputjni/GetPutJNI.cpp @@ -377,7 +377,7 @@ jint Java_com_evolvedbinary_jnibench_common_getputjni_GetPutJNI_getIntoIndirectB return get_size; } -jint getIntoRawPointer(const char* key, char* dest, int dest_len) { +jint getIntoMemorySegment(const char* key, char* dest, int dest_len) { std::string value = GetByteArrayInternal(key); int size = std::min((int)value.size(), dest_len); memcpy(dest, value.c_str(), size); From c6599977484e041c765b0eb4224614e1605f5b6d Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 21:21:13 +0000 Subject: [PATCH 17/32] POC for Get with FFP --- .../jnibench/jmhbench/GetJNIBenchmark.java | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java b/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java index fe0e2a4..0996804 100644 --- a/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java +++ b/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java @@ -26,9 +26,6 @@ */ package com.evolvedbinary.jnibench.jmhbench; -import static java.lang.foreign.ValueLayout.ADDRESS; -import static java.lang.foreign.ValueLayout.JAVA_INT; - import com.evolvedbinary.jnibench.common.getputjni.GetPutJNI; import com.evolvedbinary.jnibench.consbench.NarSystem; import com.evolvedbinary.jnibench.jmhbench.cache.AllocationCache; @@ -44,6 +41,9 @@ import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.Linker; import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.Date; @@ -79,8 +79,24 @@ public class GetJNIBenchmark { private static final Logger LOG = Logger.getLogger(GetJNIBenchmark.class.getName()); + private static final MethodHandle GET_INTO_MEMORY_SEGMENT_HANDLE; + static { NarSystem.loadLibrary(); + +// 2. Initialize the Linker and Lookup + Linker linker = Linker.nativeLinker(); + SymbolLookup loaderLookup = SymbolLookup.loaderLookup(); + + // 3. Find the symbol and create the Downcall Handle once + GET_INTO_MEMORY_SEGMENT_HANDLE = loaderLookup.find("getIntoMemorySegment") + .map(symbol -> linker.downcallHandle(symbol, + FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT))) + .orElseThrow(); } @State(Scope.Benchmark) @@ -264,15 +280,9 @@ public void getIntoUnsafe(GetJNIBenchmarkState benchmarkState, GetJNIThreadState public void getIntoMemorySegment(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { final var segment = threadState.memorySegmentCache.acquire(); - final var linker = Linker.nativeLinker(); - final var symbol = linker.defaultLookup() - .find("getIntoMemorySegment") - .orElseThrow(); - final var methodHandle = linker.downcallHandle(symbol, FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS, JAVA_INT)); - // Call via Panama MethodHandle try { - methodHandle.invokeExact( + GET_INTO_MEMORY_SEGMENT_HANDLE.invokeExact( benchmarkState.keyMemorySegment, // Pre-allocated segment for key segment, benchmarkState.valueSize From 8dc796f7724982afa550d5e3a88654fa95fe6ea1 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 21:22:57 +0000 Subject: [PATCH 18/32] POC for Get with FFP --- src/main/c++/getputjni/GetPutJNI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/c++/getputjni/GetPutJNI.cpp b/src/main/c++/getputjni/GetPutJNI.cpp index c235454..8909f38 100644 --- a/src/main/c++/getputjni/GetPutJNI.cpp +++ b/src/main/c++/getputjni/GetPutJNI.cpp @@ -377,7 +377,7 @@ jint Java_com_evolvedbinary_jnibench_common_getputjni_GetPutJNI_getIntoIndirectB return get_size; } -jint getIntoMemorySegment(const char* key, char* dest, int dest_len) { +extern "C" int getIntoMemorySegment(const char* key, char* dest, int dest_len) { std::string value = GetByteArrayInternal(key); int size = std::min((int)value.size(), dest_len); memcpy(dest, value.c_str(), size); From 255b170457fdddfd457e08ec6f1d64b53517dd8f Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 21:33:41 +0000 Subject: [PATCH 19/32] POC for Get with FFP --- .../com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java b/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java index 0996804..b3e04c4 100644 --- a/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java +++ b/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java @@ -282,11 +282,12 @@ public void getIntoMemorySegment(GetJNIBenchmarkState benchmarkState, GetJNIThre final var segment = threadState.memorySegmentCache.acquire(); try { - GET_INTO_MEMORY_SEGMENT_HANDLE.invokeExact( + final var size = (int) GET_INTO_MEMORY_SEGMENT_HANDLE.invokeExact( benchmarkState.keyMemorySegment, // Pre-allocated segment for key segment, benchmarkState.valueSize ); + blackhole.consume(size); } catch (Throwable e) { throw new RuntimeException(e); } From 6f2852899f23c83b91239e715ac89a0a8fd2caf6 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 21:45:32 +0000 Subject: [PATCH 20/32] POC for Get with FFP --- .../jnibench/jmhbench/GetJNIBenchmark.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java b/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java index b3e04c4..751c4eb 100644 --- a/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java +++ b/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java @@ -38,6 +38,7 @@ import com.evolvedbinary.jnibench.jmhbench.common.JMHCaller; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; +import java.lang.foreign.Arena; import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.Linker; import java.lang.foreign.MemorySegment; @@ -128,25 +129,28 @@ public static class GetJNIBenchmarkState { String keyBase; byte[] keyBytes; - private MemorySegment keyMemorySegment; + private Arena arena; JMHCaller caller; @Setup public void setup() { this.caller = JMHCaller.fromStack(); + arena = Arena.ofShared(); keyBase = "testKeyWithReturnValueSize" + String.format("%07d", valueSize) + "Bytes"; keyBytes = keyBase.getBytes(); - keyMemorySegment = MemorySegment.ofArray(keyBytes); + arena.allocateArray(ValueLayout.JAVA_BYTE, keyBytes); readChecksum = AllocationCache.Checksum.valueOf(checksum); } @TearDown public void tearDown() { - + if (arena != null) { + arena.close(); + } } } From 9802d027a82255c546a689511aa73c86cb027934 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 2 Jan 2026 21:46:51 +0000 Subject: [PATCH 21/32] POC for Get with FFP --- .../com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java b/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java index 751c4eb..e665cd0 100644 --- a/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java +++ b/src/main/java/com/evolvedbinary/jnibench/jmhbench/GetJNIBenchmark.java @@ -130,6 +130,7 @@ public static class GetJNIBenchmarkState { String keyBase; byte[] keyBytes; private Arena arena; + private MemorySegment keyMemorySegment; JMHCaller caller; @@ -141,7 +142,7 @@ public void setup() { keyBase = "testKeyWithReturnValueSize" + String.format("%07d", valueSize) + "Bytes"; keyBytes = keyBase.getBytes(); - arena.allocateArray(ValueLayout.JAVA_BYTE, keyBytes); + keyMemorySegment = arena.allocateArray(ValueLayout.JAVA_BYTE, keyBytes); readChecksum = AllocationCache.Checksum.valueOf(checksum); } From 6f0e8b4be6f6c22a8fabcfe5a24ca06c09cc474b Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:55:07 +0000 Subject: [PATCH 22/32] Add subselection information to graph titles. --- jmh_plot.json | 35 +++++++++++++++++++++++------------ jmhplot.py | 13 +++++++------ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/jmh_plot.json b/jmh_plot.json index da9d398..58c0b2e 100644 --- a/jmh_plot.json +++ b/jmh_plot.json @@ -5,7 +5,8 @@ "name": "valueSize", "min": 1024 }, - "label": "allbig" + "label": "allbig", + "subselection": ">= 1024" }, { "xaxisparam": { @@ -13,7 +14,8 @@ "min": 1, "max": 4096 }, - "label": "allsmall" + "label": "allsmall", + "subselection": "<= 4096" # defaults to include_patterns of all matching }, { @@ -23,7 +25,8 @@ "max": 4096 }, "exclude_patterns": ["Pooled"], - "label": "nopoolsmall" + "label": "nopoolsmall", + "subselection": "<= 4096 (excluding Pooled)" }, { "xaxisparam": { @@ -31,57 +34,65 @@ "min": 1024 }, "exclude_patterns": ["Pooled"], - "label": "nopoolbig" + "label": "nopoolbig", + "subselection": ">= 1024 (excluding Pooled)" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["ByteArray"], - "label": "ba" + "label": "ba", + "subselection": "Byte Array" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["ByteBuf"], - "label": "bb" + "label": "bb", + "subselection": "Byte Buffer" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["Direct"], - "label": "dir" + "label": "dir", + "subselection": "Direct" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["Indirect"], - "label": "ind" + "label": "ind", + "subselection": "Indirect" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["Critical"], - "label": "crit" + "label": "crit", + "subselection": "Critical" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["GetElements"], - "label": "getelems" + "label": "getelems", + "subselection": "GetElements" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["SetRegion"], - "label": "setreg" + "label": "setreg", + "subselection": "SetRegion" } ], "result.path": "./analysis/testplots" -} \ No newline at end of file +} diff --git a/jmhplot.py b/jmhplot.py index 853fbd4..2adfea3 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -193,11 +193,11 @@ def tuple_of_secondary_keys(params: BMParams) -> Tuple: return tuple(secondaryKeys) -def plot_all_results(params: BMParams, resultSets: ResultSets, path, include_benchmarks: str, exclude_benchmarks: str, label: str) -> None: +def plot_all_results(params: BMParams, resultSets: ResultSets, path, include_benchmarks: str, exclude_benchmarks: str, label: str, subselection: str) -> None: indexKeys = tuple_of_secondary_keys(params) for indexTuple, resultSet in resultSets.items(): plot_result_set(indexKeys, indexTuple, resultSet, - path, include_benchmarks, exclude_benchmarks, label) + path, include_benchmarks, exclude_benchmarks, label, subselection) def plot_result_axis_errorbars(ax, resultSet: ResultSet) -> None: @@ -256,7 +256,7 @@ def plot_result_axis_bars(ax, resultSet: ResultSet) -> None: bmIndex = bmIndex + 1 -def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, path: pathlib.Path, include_benchmarks: str, exclude_benchmarks: str, label: str): +def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, path: pathlib.Path, include_benchmarks: str, exclude_benchmarks: str, label: str, subselection: str): # Determine how many colors we need num_benchmarks = len(resultSet) @@ -274,8 +274,8 @@ def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, p plot_result_axis_bars(ax, resultSet) plt.suptitle("x86_64 - Xeon E5-1650 v3 @ 3.50GHz - 128GB ECC RAM - Ubuntu 24.04.3 LTS - Kernel: 6.14.0-36-generic") - plt.title( - f'{str(indexKeys)}={str(indexTuple)} include={include_benchmarks} exclude={exclude_benchmarks}') + title = f'{str(indexKeys)}={str(indexTuple)} include={include_benchmarks} exclude={exclude_benchmarks} subselection={subselection}' + plt.title(title) plt.xlabel("# Operations") plt.ylabel("t (ns)") plt.legend(loc='upper left', bbox_to_anchor=(1, 1)) @@ -348,6 +348,7 @@ def process_some_plots(path: pathlib.Path, plot: Dict) -> None: include_benchmarks = optional('include_patterns', plot) exclude_benchmarks = optional('exclude_patterns', plot) label = required('label', plot) + subselection = optional('subselection', plot) dataframe = normalize_data_frame_from_path(path) if len(dataframe) == 0: @@ -369,7 +370,7 @@ def process_some_plots(path: pathlib.Path, plot: Dict) -> None: extract_params(dataframe), primary_param_name) resultSets = extract_results_per_param(dataframe, params) plot_all_results(params, resultSets, path, - include_benchmarks, exclude_benchmarks, label) + include_benchmarks, exclude_benchmarks, label, subselection) def process_benchmarks(config: Dict) -> None: From 7e033d53ca5c40d83ac162bfeb8f034ecafe918e Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:59:19 +0000 Subject: [PATCH 23/32] Extract System information into title --- jmhplot.py | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/jmhplot.py b/jmhplot.py index 2adfea3..460b0e7 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -256,6 +256,76 @@ def plot_result_axis_bars(ax, resultSet: ResultSet) -> None: bmIndex = bmIndex + 1 +def get_system_info() -> str: + try: + import platform + import subprocess + + arch = platform.machine() + system = platform.system() + kernel = platform.release() + + cpu_model = "" + ram_info = "" + os_info = "" + + if system == "Darwin": + try: + cpu_model = subprocess.check_output(['sysctl', '-n', 'machdep.cpu.brand_string']).decode().strip() + except Exception: + cpu_model = platform.processor() + + try: + mem_bytes = int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']).decode().strip()) + ram_info = f"{mem_bytes // (1024**3)}GB RAM" + except Exception: + ram_info = "Unknown RAM" + + os_info = f"macOS {platform.mac_ver()[0]}" + + elif system == "Linux": + try: + with open("/proc/cpuinfo", "r") as f: + for line in f: + if "model name" in line: + cpu_model = line.split(":")[1].strip() + break + except Exception: + cpu_model = platform.processor() + + try: + with open("/proc/meminfo", "r") as f: + for line in f: + if "MemTotal" in line: + mem_kb = int(line.split(":")[1].strip().split()[0]) + ram_info = f"{mem_kb // (1024**2)}GB RAM" + break + except Exception: + ram_info = "Unknown RAM" + + try: + import lsb_release + os_info = lsb_release.get_distro_information()['DESCRIPTION'] + except Exception: + try: + with open("/etc/os-release", "r") as f: + for line in f: + if line.startswith("PRETTY_NAME="): + os_info = line.split("=")[1].strip().strip('"') + break + except Exception: + os_info = f"Linux {platform.release()}" + + else: + cpu_model = platform.processor() + os_info = f"{system} {platform.release()}" + + return f"{arch} - {cpu_model} - {ram_info} - {os_info} - Kernel: {kernel}" + + except Exception as e: + return f"Unknown System - {str(e)}" + + def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, path: pathlib.Path, include_benchmarks: str, exclude_benchmarks: str, label: str, subselection: str): # Determine how many colors we need num_benchmarks = len(resultSet) @@ -273,7 +343,7 @@ def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, p plot_result_axis_bars(ax, resultSet) - plt.suptitle("x86_64 - Xeon E5-1650 v3 @ 3.50GHz - 128GB ECC RAM - Ubuntu 24.04.3 LTS - Kernel: 6.14.0-36-generic") + plt.suptitle(get_system_info()) title = f'{str(indexKeys)}={str(indexTuple)} include={include_benchmarks} exclude={exclude_benchmarks} subselection={subselection}' plt.title(title) plt.xlabel("# Operations") From d5b1e58059740a0754c910f206164d8f3215a074 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:32:30 +0000 Subject: [PATCH 24/32] Read system information during jmhrun in order to ensure that running jmhplot on a different system doesn't impact the result --- jmhplot.py | 101 +++++++++++++---------------------------------------- jmhrun.py | 81 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 76 deletions(-) diff --git a/jmhplot.py b/jmhplot.py index 460b0e7..083f097 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -193,11 +193,11 @@ def tuple_of_secondary_keys(params: BMParams) -> Tuple: return tuple(secondaryKeys) -def plot_all_results(params: BMParams, resultSets: ResultSets, path, include_benchmarks: str, exclude_benchmarks: str, label: str, subselection: str) -> None: +def plot_all_results(params: BMParams, resultSets: ResultSets, path, include_benchmarks: str, exclude_benchmarks: str, label: str, subselection: str, system_info: str) -> None: indexKeys = tuple_of_secondary_keys(params) for indexTuple, resultSet in resultSets.items(): plot_result_set(indexKeys, indexTuple, resultSet, - path, include_benchmarks, exclude_benchmarks, label, subselection) + path, include_benchmarks, exclude_benchmarks, label, subselection, system_info) def plot_result_axis_errorbars(ax, resultSet: ResultSet) -> None: @@ -256,77 +256,7 @@ def plot_result_axis_bars(ax, resultSet: ResultSet) -> None: bmIndex = bmIndex + 1 -def get_system_info() -> str: - try: - import platform - import subprocess - - arch = platform.machine() - system = platform.system() - kernel = platform.release() - - cpu_model = "" - ram_info = "" - os_info = "" - - if system == "Darwin": - try: - cpu_model = subprocess.check_output(['sysctl', '-n', 'machdep.cpu.brand_string']).decode().strip() - except Exception: - cpu_model = platform.processor() - - try: - mem_bytes = int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']).decode().strip()) - ram_info = f"{mem_bytes // (1024**3)}GB RAM" - except Exception: - ram_info = "Unknown RAM" - - os_info = f"macOS {platform.mac_ver()[0]}" - - elif system == "Linux": - try: - with open("/proc/cpuinfo", "r") as f: - for line in f: - if "model name" in line: - cpu_model = line.split(":")[1].strip() - break - except Exception: - cpu_model = platform.processor() - - try: - with open("/proc/meminfo", "r") as f: - for line in f: - if "MemTotal" in line: - mem_kb = int(line.split(":")[1].strip().split()[0]) - ram_info = f"{mem_kb // (1024**2)}GB RAM" - break - except Exception: - ram_info = "Unknown RAM" - - try: - import lsb_release - os_info = lsb_release.get_distro_information()['DESCRIPTION'] - except Exception: - try: - with open("/etc/os-release", "r") as f: - for line in f: - if line.startswith("PRETTY_NAME="): - os_info = line.split("=")[1].strip().strip('"') - break - except Exception: - os_info = f"Linux {platform.release()}" - - else: - cpu_model = platform.processor() - os_info = f"{system} {platform.release()}" - - return f"{arch} - {cpu_model} - {ram_info} - {os_info} - Kernel: {kernel}" - - except Exception as e: - return f"Unknown System - {str(e)}" - - -def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, path: pathlib.Path, include_benchmarks: str, exclude_benchmarks: str, label: str, subselection: str): +def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, path: pathlib.Path, include_benchmarks: str, exclude_benchmarks: str, label: str, subselection: str, system_info: str): # Determine how many colors we need num_benchmarks = len(resultSet) @@ -343,7 +273,7 @@ def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, p plot_result_axis_bars(ax, resultSet) - plt.suptitle(get_system_info()) + plt.suptitle(system_info) title = f'{str(indexKeys)}={str(indexTuple)} include={include_benchmarks} exclude={exclude_benchmarks} subselection={subselection}' plt.title(title) plt.xlabel("# Operations") @@ -354,7 +284,7 @@ def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, p name = f'fig_{"_".join([str(t) for t in indexTuple])}_{label}.png' if path.is_file(): - path = path.parent() + path = path.parent fig.savefig(path.joinpath(name), bbox_inches='tight') @@ -420,6 +350,25 @@ def process_some_plots(path: pathlib.Path, plot: Dict) -> None: label = required('label', plot) subselection = optional('subselection', plot) + # Check for system_info.json in the path + system_info = None + system_info_file = None + if path.is_dir(): + system_info_file = path.joinpath('system_info.json') + if path.is_file(): + system_info_file = path.parent.joinpath('system_info.json') + + if system_info_file and system_info_file.exists(): + try: + with system_info_file.open(mode='r', encoding='UTF-8') as f: + info_json = json.load(f) + system_info = info_json.get('system_info') + except Exception: + pass + + if system_info is None: + system_info = "System Info unavailable" + dataframe = normalize_data_frame_from_path(path) if len(dataframe) == 0: raise RunnerError( @@ -440,7 +389,7 @@ def process_some_plots(path: pathlib.Path, plot: Dict) -> None: extract_params(dataframe), primary_param_name) resultSets = extract_results_per_param(dataframe, params) plot_all_results(params, resultSets, path, - include_benchmarks, exclude_benchmarks, label, subselection) + include_benchmarks, exclude_benchmarks, label, subselection, system_info) def process_benchmarks(config: Dict) -> None: diff --git a/jmhrun.py b/jmhrun.py index ebc9bd7..771c026 100755 --- a/jmhrun.py +++ b/jmhrun.py @@ -32,6 +32,7 @@ import pathlib import json import subprocess +import platform from typing import Dict @@ -112,6 +113,81 @@ def output_options(config: Dict) -> list: return ['-rff', str(path.joinpath(pathlib.Path(f'jmh_{const_datetime_str}.csv')))] +def get_system_info() -> str: + try: + arch = platform.machine() + system = platform.system() + kernel = platform.release() + + cpu_model = "" + ram_info = "" + os_info = "" + java_info = "" + + try: + java_version_out = subprocess.check_output(['java', '-version'], stderr=subprocess.STDOUT).decode().strip() + # The first line usually contains the version information + java_info = java_version_out.splitlines()[0] + except Exception: + java_info = "Unknown Java" + + if system == "Darwin": + try: + cpu_model = subprocess.check_output(['sysctl', '-n', 'machdep.cpu.brand_string']).decode().strip() + except Exception: + cpu_model = platform.processor() + + try: + mem_bytes = int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']).decode().strip()) + ram_info = f"{mem_bytes // (1024**3)}GB RAM" + except Exception: + ram_info = "Unknown RAM" + + os_info = f"macOS {platform.mac_ver()[0]}" + + elif system == "Linux": + try: + with open("/proc/cpuinfo", "r") as f: + for line in f: + if "model name" in line: + cpu_model = line.split(":")[1].strip() + break + except Exception: + cpu_model = platform.processor() + + try: + with open("/proc/meminfo", "r") as f: + for line in f: + if "MemTotal" in line: + mem_kb = int(line.split(":")[1].strip().split()[0]) + ram_info = f"{mem_kb // (1024**2)}GB RAM" + break + except Exception: + ram_info = "Unknown RAM" + + try: + import lsb_release + os_info = lsb_release.get_distro_information()['DESCRIPTION'] + except Exception: + try: + with open("/etc/os-release", "r") as f: + for line in f: + if line.startswith("PRETTY_NAME="): + os_info = line.split("=")[1].strip().strip('"') + break + except Exception: + os_info = f"Linux {platform.release()}" + + else: + cpu_model = platform.processor() + os_info = f"{system} {platform.release()}" + + return f"{arch} - {cpu_model} - {ram_info} - {os_info} - Kernel: {kernel} - {java_info}" + + except Exception as e: + return f"Unknown System - {str(e)}" + + def build_jmh_command(config: Dict) -> list: cmd = ["java"] @@ -192,6 +268,11 @@ def log_jmh_session(cmd: list, config: Dict, config_file: str): log.writelines(line + '\n' for line in ['```', '#### Command', 'The java command executed to run the tests', '```', ' '.join(cmd), '```']) + # Save system info + system_info_file = output_dir_path(config).joinpath('system_info.json') + with system_info_file.open(mode='w', encoding='UTF-8') as f: + json.dump({"system_info": get_system_info()}, f, indent=4) + def exec_jmh_cmd(cmd: list, help_requested): cmd_str = ' '.join(cmd) From dcb5c4729dbf619551049b1257884743b5cf3280 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:32:46 +0000 Subject: [PATCH 25/32] Put quotes around subselection in order to reduce confusion when using symbols. --- jmhplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmhplot.py b/jmhplot.py index 083f097..1e1d4de 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -274,7 +274,7 @@ def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, p plot_result_axis_bars(ax, resultSet) plt.suptitle(system_info) - title = f'{str(indexKeys)}={str(indexTuple)} include={include_benchmarks} exclude={exclude_benchmarks} subselection={subselection}' + title = f'{str(indexKeys)}={str(indexTuple)} include={include_benchmarks} exclude={exclude_benchmarks} subselection="{subselection}"' plt.title(title) plt.xlabel("# Operations") plt.ylabel("t (ns)") From 85877b95aa4920c867707cb53468b5a6e072ad51 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:08:03 +0000 Subject: [PATCH 26/32] Add MemorySegment function to "Put" benchmark. --- src/main/c++/getputjni/GetPutJNI.cpp | 6 ++ .../jnibench/jmhbench/PutJNIBenchmark.java | 72 ++++++++++++++++--- 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/src/main/c++/getputjni/GetPutJNI.cpp b/src/main/c++/getputjni/GetPutJNI.cpp index 8909f38..e4b67c8 100644 --- a/src/main/c++/getputjni/GetPutJNI.cpp +++ b/src/main/c++/getputjni/GetPutJNI.cpp @@ -384,6 +384,12 @@ extern "C" int getIntoMemorySegment(const char* key, char* dest, int dest_len) { return size; } +extern "C" int putFromMemorySegment(const char* key, const char* src, int src_len) { + char *db_buf = GetByteArrayInternalForWrite(key, src_len); + memcpy(db_buf, src, src_len); + return src_len; +} + /* * Class: com_evolvedbinary_jnibench_common_getputjni_GetPutJNI * Method: putFromIndirectByteBufferGetRegion diff --git a/src/main/java/com/evolvedbinary/jnibench/jmhbench/PutJNIBenchmark.java b/src/main/java/com/evolvedbinary/jnibench/jmhbench/PutJNIBenchmark.java index e3b3355..d2f242a 100644 --- a/src/main/java/com/evolvedbinary/jnibench/jmhbench/PutJNIBenchmark.java +++ b/src/main/java/com/evolvedbinary/jnibench/jmhbench/PutJNIBenchmark.java @@ -30,20 +30,26 @@ import com.evolvedbinary.jnibench.consbench.NarSystem; import com.evolvedbinary.jnibench.jmhbench.cache.*; import com.evolvedbinary.jnibench.jmhbench.common.*; +import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.nio.ByteBuffer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; -import io.netty.buffer.ByteBuf; - -import java.nio.ByteBuffer; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; /** * Benchmark getting byte arrays from native methods. @@ -58,8 +64,24 @@ public class PutJNIBenchmark { private static final Logger LOG = Logger.getLogger(GetJNIBenchmark.class.getName()); + private static final MethodHandle PUT_FROM_MEMORY_SEGMENT_HANDLE; + static { NarSystem.loadLibrary(); + + // 2. Initialize the Linker and Lookup + Linker linker = Linker.nativeLinker(); + SymbolLookup loaderLookup = SymbolLookup.loaderLookup(); + + // 3. Find the symbol and create the Downcall Handle once + PUT_FROM_MEMORY_SEGMENT_HANDLE = loaderLookup.find("putFromMemorySegment") + .map(symbol -> linker.downcallHandle(symbol, + FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT))) + .orElseThrow(); } @State(Scope.Benchmark) @@ -90,6 +112,8 @@ public static class GetJNIBenchmarkState { String keyBase; byte[] keyBytes; + MemorySegment keyMemorySegment; + private Arena benchmarkArena; JMHCaller caller; @@ -99,14 +123,19 @@ public void setup() { keyBase = "testKeyWithReturnValueSize" + String.format("%07d", valueSize) + "Bytes"; + benchmarkArena = Arena.ofShared(); + keyBytes = keyBase.getBytes(); + keyMemorySegment = benchmarkArena.allocateArray(ValueLayout.JAVA_BYTE, keyBytes); writePreparation = AllocationCache.Prepare.valueOf(preparation); } @TearDown public void tearDown() { - + if (benchmarkArena != null) { + benchmarkArena.close(); + } } } @@ -117,6 +146,7 @@ public static class GetJNIThreadState { private final UnsafeBufferCache unsafeBufferCache = new UnsafeBufferCache(); private final ByteArrayCache byteArrayCache = new ByteArrayCache(); private final IndirectByteBufferCache indirectByteBufferCache = new IndirectByteBufferCache(); + private final MemorySegmentCache memorySegmentCache = new MemorySegmentCache(); private final PooledByteBufAllocator pooledByteBufAllocator = PooledByteBufAllocator.DEFAULT; private final NettyByteBufCache nettyByteBufCache = new NettyByteBufCache(); @@ -152,6 +182,9 @@ public void setup(GetJNIBenchmarkState benchmarkState, Blackhole blackhole) { case "putFromByteArrayCritical": byteArrayCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, benchmarkState.writePreparation, blackhole); break; + case "putFromMemorySegment": + memorySegmentCache.setup(valueSize, cacheSize, benchmarkState.cacheEntryOverhead, benchmarkState.writePreparation, blackhole); + break; default: throw new RuntimeException("Don't know how to setup() for benchmark: " + benchmarkState.caller.benchmarkMethod); } @@ -184,6 +217,9 @@ public void tearDown(GetJNIBenchmarkState benchmarkState) { case "putFromByteArrayCritical": byteArrayCache.tearDown(); break; + case "putFromMemorySegment": + memorySegmentCache.tearDown(); + break; default: throw new RuntimeException("Don't know how to tearDown() for benchmark: " + benchmarkState.caller.benchmarkMethod); } @@ -196,6 +232,26 @@ public void buffersOnlyDirectByteBufferFromUnsafe(GetJNIThreadState threadState) threadState.unsafeBufferCache.release(unsafeBuffer); } + @Benchmark + public void putFromMemorySegment(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, + Blackhole blackhole) { + final var segment = threadState.memorySegmentCache.acquire(); + threadState.memorySegmentCache.prepareBuffer(segment, benchmarkState.fillByte); + + try { + final var size = (int) PUT_FROM_MEMORY_SEGMENT_HANDLE.invokeExact( + benchmarkState.keyMemorySegment, // Pre-allocated segment for key + segment, + benchmarkState.valueSize + ); + blackhole.consume(size); + } catch (Throwable e) { + throw new RuntimeException(e); + } + + threadState.memorySegmentCache.release(segment); + } + @Benchmark public void putFromDirectByteBuffer(GetJNIBenchmarkState benchmarkState, GetJNIThreadState threadState, Blackhole blackhole) { ByteBuffer byteBuffer = threadState.directByteBufferCache.acquire(); From b54e1473b0d24e425d70b1c8106016c4492ebee3 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:34:26 +0000 Subject: [PATCH 27/32] Explicitly list value size --- jmh_plot.json | 29 +++++++++++------------------ jmhplot.py | 30 +++++++++++++++++++----------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/jmh_plot.json b/jmh_plot.json index 58c0b2e..6dd96a9 100644 --- a/jmh_plot.json +++ b/jmh_plot.json @@ -6,7 +6,7 @@ "min": 1024 }, "label": "allbig", - "subselection": ">= 1024" + "valueSizeTitle": ">= 1024" }, { "xaxisparam": { @@ -15,7 +15,7 @@ "max": 4096 }, "label": "allsmall", - "subselection": "<= 4096" + "valueSizeTitle": "<= 4096", # defaults to include_patterns of all matching }, { @@ -26,7 +26,7 @@ }, "exclude_patterns": ["Pooled"], "label": "nopoolsmall", - "subselection": "<= 4096 (excluding Pooled)" + "valueSizeTitle": "<= 4096" }, { "xaxisparam": { @@ -35,63 +35,56 @@ }, "exclude_patterns": ["Pooled"], "label": "nopoolbig", - "subselection": ">= 1024 (excluding Pooled)" + "valueSizeTitle": ">= 1024" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["ByteArray"], - "label": "ba", - "subselection": "Byte Array" + "label": "ba" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["ByteBuf"], - "label": "bb", - "subselection": "Byte Buffer" + "label": "bb" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["Direct"], - "label": "dir", - "subselection": "Direct" + "label": "dir" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["Indirect"], - "label": "ind", - "subselection": "Indirect" + "label": "ind" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["Critical"], - "label": "crit", - "subselection": "Critical" + "label": "crit" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["GetElements"], - "label": "getelems", - "subselection": "GetElements" + "label": "getelems" }, { "xaxisparam": { "name": "valueSize" }, "include_patterns": ["SetRegion"], - "label": "setreg", - "subselection": "SetRegion" + "label": "setreg" } ], "result.path": "./analysis/testplots" diff --git a/jmhplot.py b/jmhplot.py index 1e1d4de..91a8aac 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -193,11 +193,11 @@ def tuple_of_secondary_keys(params: BMParams) -> Tuple: return tuple(secondaryKeys) -def plot_all_results(params: BMParams, resultSets: ResultSets, path, include_benchmarks: str, exclude_benchmarks: str, label: str, subselection: str, system_info: str) -> None: +def plot_all_results(params: BMParams, xaxisparam:Dict, result_sets: ResultSets, path, include_benchmarks: str, exclude_benchmarks: str, label: str, value_size_title: str, system_info: str) -> None: indexKeys = tuple_of_secondary_keys(params) - for indexTuple, resultSet in resultSets.items(): - plot_result_set(indexKeys, indexTuple, resultSet, - path, include_benchmarks, exclude_benchmarks, label, subselection, system_info) + for indexTuple, resultSet in result_sets.items(): + plot_result_set(xaxisparam, indexKeys, indexTuple, resultSet, + path, include_benchmarks, exclude_benchmarks, label, value_size_title, system_info) def plot_result_axis_errorbars(ax, resultSet: ResultSet) -> None: @@ -256,7 +256,7 @@ def plot_result_axis_bars(ax, resultSet: ResultSet) -> None: bmIndex = bmIndex + 1 -def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, path: pathlib.Path, include_benchmarks: str, exclude_benchmarks: str, label: str, subselection: str, system_info: str): +def plot_result_set(xaxisparam:Dict, indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, path: pathlib.Path, include_benchmarks: str, exclude_benchmarks: str, label: str, value_size_title: str, system_info: str): # Determine how many colors we need num_benchmarks = len(resultSet) @@ -274,9 +274,9 @@ def plot_result_set(indexKeys: Tuple, indexTuple: Tuple, resultSet: ResultSet, p plot_result_axis_bars(ax, resultSet) plt.suptitle(system_info) - title = f'{str(indexKeys)}={str(indexTuple)} include={include_benchmarks} exclude={exclude_benchmarks} subselection="{subselection}"' + title = f'{str(indexKeys)}={str(indexTuple)} include={include_benchmarks} exclude={exclude_benchmarks} Value Size="{value_size_title}"' plt.title(title) - plt.xlabel("# Operations") + plt.xlabel(extract_parameter_name(xaxisparam)) plt.ylabel("t (ns)") plt.legend(loc='upper left', bbox_to_anchor=(1, 1)) plt.grid(visible='True', which='both') @@ -322,7 +322,7 @@ def filter_for_benchmarks(dataframe: DataFrame, include_benchmarks, exclude_benc def filter_for_range(dataframe: DataFrame, xaxisparam: Dict) -> DataFrame: - param_name = required('name', xaxisparam) + param_name = extract_parameter_name(xaxisparam) xmin = optional('min', xaxisparam, lambda x: int(x)) xmax = optional('max', xaxisparam, lambda x: int(x)) if xmax is None and xmin is None: @@ -340,6 +340,14 @@ def filter_for_range(dataframe: DataFrame, xaxisparam: Dict) -> DataFrame: lambda x: int(x) >= xmin and int(x) <= xmax)] +def extract_parameter_name(xaxisparam): + return required('name', xaxisparam) + + +def default_if_none(optional_string, default_value: str) -> str: + return default_value if optional_string is None else optional_string + + def process_some_plots(path: pathlib.Path, plot: Dict) -> None: xaxisparam = required('xaxisparam', plot) @@ -348,7 +356,7 @@ def process_some_plots(path: pathlib.Path, plot: Dict) -> None: include_benchmarks = optional('include_patterns', plot) exclude_benchmarks = optional('exclude_patterns', plot) label = required('label', plot) - subselection = optional('subselection', plot) + value_size_title = default_if_none(optional('valueSizeTitle', plot), "All") # Check for system_info.json in the path system_info = None @@ -388,8 +396,8 @@ def process_some_plots(path: pathlib.Path, plot: Dict) -> None: params: BMParams = split_params( extract_params(dataframe), primary_param_name) resultSets = extract_results_per_param(dataframe, params) - plot_all_results(params, resultSets, path, - include_benchmarks, exclude_benchmarks, label, subselection, system_info) + plot_all_results(params, xaxisparam, resultSets, path, + include_benchmarks, exclude_benchmarks, label, value_size_title, system_info) def process_benchmarks(config: Dict) -> None: From 70df923ed66a79c9b797c024fffc1b888f70c07a Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:36:41 +0000 Subject: [PATCH 28/32] Explicitly list value size --- jmhplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmhplot.py b/jmhplot.py index 91a8aac..ba3c800 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -341,7 +341,7 @@ def filter_for_range(dataframe: DataFrame, xaxisparam: Dict) -> DataFrame: def extract_parameter_name(xaxisparam): - return required('name', xaxisparam) + return optional('name', xaxisparam) def default_if_none(optional_string, default_value: str) -> str: From aab607239cea9de30fff75962d5af816c20d3a9b Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:38:01 +0000 Subject: [PATCH 29/32] Explicitly list value size --- jmh_plot.json | 2 +- jmhplot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jmh_plot.json b/jmh_plot.json index 6dd96a9..cb7aa90 100644 --- a/jmh_plot.json +++ b/jmh_plot.json @@ -15,7 +15,7 @@ "max": 4096 }, "label": "allsmall", - "valueSizeTitle": "<= 4096", + "valueSizeTitle": "<= 4096" # defaults to include_patterns of all matching }, { diff --git a/jmhplot.py b/jmhplot.py index ba3c800..91a8aac 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -341,7 +341,7 @@ def filter_for_range(dataframe: DataFrame, xaxisparam: Dict) -> DataFrame: def extract_parameter_name(xaxisparam): - return optional('name', xaxisparam) + return required('name', xaxisparam) def default_if_none(optional_string, default_value: str) -> str: From e70a4418024c56940b43a177268502f4185e2d6d Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Tue, 6 Jan 2026 11:12:35 +0000 Subject: [PATCH 30/32] Provide Java 21 versions for all run configs. --- jmh_full_get_java21.json | 19 +++++++++++++++++++ jmh_full_put_java21.json | 19 +++++++++++++++++++ jmh_small_get_java21.json | 19 +++++++++++++++++++ jmh_small_put_java21.json | 19 +++++++++++++++++++ jmh_tiny_put_java21.json | 19 +++++++++++++++++++ 5 files changed, 95 insertions(+) create mode 100644 jmh_full_get_java21.json create mode 100644 jmh_full_put_java21.json create mode 100644 jmh_small_get_java21.json create mode 100644 jmh_small_put_java21.json create mode 100644 jmh_tiny_put_java21.json diff --git a/jmh_full_get_java21.json b/jmh_full_get_java21.json new file mode 100644 index 0000000..8ac77f4 --- /dev/null +++ b/jmh_full_get_java21.json @@ -0,0 +1,19 @@ +{ + "benchmark": "GetJNIBenchmark", + "jvmargs": ["Xmx4G", "XX:ErrorFile=./results/hs_err_pid%p.log", "XX:+HeapDumpOnOutOfMemoryError", "-enable-preview"],, + "params": { + "valueSize": [10, 50, 512, 1024, 4096, 8192, 16384, 32768, 65536], + "cacheMB": [1], + "checksum": ["none", "copyout"] + }, + "options": { + "batchsize": 1, + "warmupiterations": 20, + "warmuptime": "50ms", + "iterations": 50, + "time": "500ms" + }, + "result.path": "./results", + "java.library.path": "target/jni-benchmarks-1.0.1-SNAPSHOT-application/jni-benchmarks-1.0.1-SNAPSHOT/lib", + "jar": "target/jni-benchmarks-1.0.1-SNAPSHOT-benchmarks.nar" +} diff --git a/jmh_full_put_java21.json b/jmh_full_put_java21.json new file mode 100644 index 0000000..592c75d --- /dev/null +++ b/jmh_full_put_java21.json @@ -0,0 +1,19 @@ +{ + "benchmark": "PutJNIBenchmark", + "jvmargs": ["Xmx4G", "XX:ErrorFile=./results/hs_err_pid%p.log", "XX:+HeapDumpOnOutOfMemoryError", "-enable-preview"], + "params": { + "valueSize": [10, 50, 512, 1024, 4096, 8192, 16384, 32768, 65536], + "cacheMB": [1], + "checksum": ["none", "copyin"] + }, + "options": { + "batchsize": 1, + "warmupiterations": 20, + "warmuptime": "50ms", + "iterations": 50, + "time": "500ms" + }, + "result.path": "./results", + "java.library.path": "target/jni-benchmarks-1.0.1-SNAPSHOT-application/jni-benchmarks-1.0.1-SNAPSHOT/lib", + "jar": "target/jni-benchmarks-1.0.1-SNAPSHOT-benchmarks.nar" +} diff --git a/jmh_small_get_java21.json b/jmh_small_get_java21.json new file mode 100644 index 0000000..a13c410 --- /dev/null +++ b/jmh_small_get_java21.json @@ -0,0 +1,19 @@ +{ + "benchmark": "GetJNIBenchmark", + "jvmargs": ["Xmx4G", "XX:ErrorFile=./results/hs_err_pid%p.log", "XX:+HeapDumpOnOutOfMemoryError", "-enable-preview"], + "params": { + "valueSize": [10, 50, 512, 1024, 4096, 8192, 16384, 32768, 65536], + "cacheMB": [1], + "checksum": ["none", "copyout"] + }, + "options": { + "batchsize": 1, + "warmupiterations": 10, + "warmuptime": "20ms", + "iterations": 20, + "time": "200ms" + }, + "result.path": "./results", + "java.library.path": "target/jni-benchmarks-1.0.1-SNAPSHOT-application/jni-benchmarks-1.0.1-SNAPSHOT/lib", + "jar": "target/jni-benchmarks-1.0.1-SNAPSHOT-benchmarks.nar" +} diff --git a/jmh_small_put_java21.json b/jmh_small_put_java21.json new file mode 100644 index 0000000..e92e8a0 --- /dev/null +++ b/jmh_small_put_java21.json @@ -0,0 +1,19 @@ +{ + "benchmark": "PutJNIBenchmark", + "jvmargs": ["Xmx4G", "XX:ErrorFile=./results/hs_err_pid%p.log", "XX:+HeapDumpOnOutOfMemoryError", "-enable-preview"], + "params": { + "valueSize": [10, 50, 512, 1024, 4096, 8192, 16384, 32768, 65536], + "cacheMB": [1], + "checksum": ["none", "copyin"] + }, + "options": { + "batchsize": 1, + "warmupiterations": 5, + "warmuptime": "20ms", + "iterations": 10, + "time": "100ms" + }, + "result.path": "./results", + "java.library.path": "target/jni-benchmarks-1.0.1-SNAPSHOT-application/jni-benchmarks-1.0.1-SNAPSHOT/lib", + "jar": "target/jni-benchmarks-1.0.1-SNAPSHOT-benchmarks.nar" +} diff --git a/jmh_tiny_put_java21.json b/jmh_tiny_put_java21.json new file mode 100644 index 0000000..e390dd5 --- /dev/null +++ b/jmh_tiny_put_java21.json @@ -0,0 +1,19 @@ +{ + "benchmark": "PutJNIBenchmark", + "jvmargs": ["Xmx4G", "XX:ErrorFile=./results/hs_err_pid%p.log", "XX:+HeapDumpOnOutOfMemoryError", "-enable-preview"], + "params": { + "valueSize": [50, 1024, 4096, 16384], + "cacheMB": [1], + "checksum": ["none", "copyin"] + }, + "options": { + "batchsize": 1, + "warmupiterations": 5, + "warmuptime": "10ms", + "iterations": 5, + "time": "50ms" + }, + "result.path": "./results", + "java.library.path": "target/jni-benchmarks-1.0.1-SNAPSHOT-application/jni-benchmarks-1.0.1-SNAPSHOT/lib", + "jar": "target/jni-benchmarks-1.0.1-SNAPSHOT-benchmarks.nar" +} From 1d92ce6eff7cccb9793dc2448b4ffaa018d8c4ed Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Tue, 6 Jan 2026 11:17:36 +0000 Subject: [PATCH 31/32] Provide Java 21 versions for all run configs. --- jmh_full_get_java21.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmh_full_get_java21.json b/jmh_full_get_java21.json index 8ac77f4..1445fff 100644 --- a/jmh_full_get_java21.json +++ b/jmh_full_get_java21.json @@ -1,6 +1,6 @@ { "benchmark": "GetJNIBenchmark", - "jvmargs": ["Xmx4G", "XX:ErrorFile=./results/hs_err_pid%p.log", "XX:+HeapDumpOnOutOfMemoryError", "-enable-preview"],, + "jvmargs": ["Xmx4G", "XX:ErrorFile=./results/hs_err_pid%p.log", "XX:+HeapDumpOnOutOfMemoryError", "-enable-preview"], "params": { "valueSize": [10, 50, 512, 1024, 4096, 8192, 16384, 32768, 65536], "cacheMB": [1], From 279e60c3b711231b499d2a5f763ecefbee957711 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:35:12 +0000 Subject: [PATCH 32/32] Add some marks to x axis. --- jmhplot.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/jmhplot.py b/jmhplot.py index 91a8aac..c626c88 100755 --- a/jmhplot.py +++ b/jmhplot.py @@ -35,6 +35,7 @@ import numpy as np import matplotlib.pyplot as plt +import matplotlib.ticker as ticker import pandas as pd from pandas.core.frame import DataFrame import re @@ -273,6 +274,12 @@ def plot_result_set(xaxisparam:Dict, indexKeys: Tuple, indexTuple: Tuple, result plot_result_axis_bars(ax, resultSet) + # Ensure more marks on the x-axis for log scale + ax.xaxis.set_major_locator(ticker.LogLocator(base=10.0, numticks=15)) + ax.xaxis.set_minor_locator(ticker.LogLocator(base=10.0, subs='auto', numticks=15)) + ax.xaxis.set_major_formatter(ticker.ScalarFormatter()) + ax.xaxis.set_minor_formatter(ticker.NullFormatter()) + plt.suptitle(system_info) title = f'{str(indexKeys)}={str(indexTuple)} include={include_benchmarks} exclude={exclude_benchmarks} Value Size="{value_size_title}"' plt.title(title)