From b3cc18dbb8158fffc8cc7b8bc26a704a62134046 Mon Sep 17 00:00:00 2001 From: Peter Lawrey Date: Tue, 28 Apr 2026 11:26:53 +0100 Subject: [PATCH] Implement support for Math.multiplyHigh in Maths class and add JMH benchmarking for XXH3 hashing --- pom.xml | 18 +++++++- src/main/java/net/openhft/hashing/Maths.java | 37 +++++---------- .../hashing/XXH3HashBytesBenchmark.java | 45 +++++++++++++++++++ 3 files changed, 73 insertions(+), 27 deletions(-) create mode 100644 src/test/java/net/openhft/hashing/XXH3HashBytesBenchmark.java diff --git a/pom.xml b/pom.xml index 714398b..b2fb805 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,9 @@ 5.1.1 all,-missing + 1.37 + openhft + https://sonarcloud.io @@ -74,6 +77,20 @@ test + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + test + + @@ -273,7 +290,6 @@ - diff --git a/src/main/java/net/openhft/hashing/Maths.java b/src/main/java/net/openhft/hashing/Maths.java index 40ed4a0..ee6c251 100644 --- a/src/main/java/net/openhft/hashing/Maths.java +++ b/src/main/java/net/openhft/hashing/Maths.java @@ -5,29 +5,26 @@ import org.jetbrains.annotations.NotNull; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Method; - +@SuppressWarnings("Since15") class Maths { @NotNull private static final Maths INSTANCE; static { - Maths maths = null; + boolean hasMultiplyHigh = true; try { - Method multiplyHigh = Math.class.getDeclaredMethod("multiplyHigh", int.class, int.class); - MethodHandle multiplyHighMH = MethodHandles.lookup().unreflect(multiplyHigh); - maths = new MathsJDK9(multiplyHighMH); + // stubs in src/main/java-stub are used to allow this to compile in Java 8+ + Math.multiplyHigh(0, 0); } catch (final Throwable ignore) { - maths = new Maths(); + hasMultiplyHigh = false; } - INSTANCE = maths; + INSTANCE = hasMultiplyHigh ? new MathsJDK9() : new Maths(); } public static long unsignedLongMulXorFold(final long lhs, final long rhs) { return INSTANCE.unsignedLongMulXorFoldImp(lhs, rhs); } + public static long unsignedLongMulHigh(final long lhs, final long rhs) { return INSTANCE.unsignedLongMulHighImp(lhs, rhs); } @@ -70,31 +67,19 @@ long unsignedLongMulHighImp(final long lhs, final long rhs) { } } +@SuppressWarnings("Since15") class MathsJDK9 extends Maths { - private final MethodHandle multiplyHighMH; - - public MathsJDK9(MethodHandle multiplyHighMH) { - this.multiplyHighMH = multiplyHighMH; - } - // Math.multiplyHigh() is intrinsified from JDK 10. But JDK 9 is out of life, we always prefer // this version to the scalar one. @Override long unsignedLongMulXorFoldImp(final long lhs, final long rhs) { - final long upper = invokeExact(lhs, rhs) + ((lhs >> 63) & rhs) + ((rhs >> 63) & lhs); + final long upper = Math.multiplyHigh(lhs, rhs) + ((lhs >> 63) & rhs) + ((rhs >> 63) & lhs); final long lower = lhs * rhs; return lower ^ upper; } + @Override long unsignedLongMulHighImp(final long lhs, final long rhs) { - return invokeExact(lhs, rhs) + ((lhs >> 63) & rhs) + ((rhs >> 63) & lhs); - } - - private long invokeExact(long lhs, long rhs) { - try { - return (long) multiplyHighMH.invokeExact(lhs, rhs); - } catch (Throwable e) { - throw new AssertionError(e); - } + return Math.multiplyHigh(lhs, rhs) + ((lhs >> 63) & rhs) + ((rhs >> 63) & lhs); } } diff --git a/src/test/java/net/openhft/hashing/XXH3HashBytesBenchmark.java b/src/test/java/net/openhft/hashing/XXH3HashBytesBenchmark.java new file mode 100644 index 0000000..582e744 --- /dev/null +++ b/src/test/java/net/openhft/hashing/XXH3HashBytesBenchmark.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0 + */ +package net.openhft.hashing; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * Reproduces issue #101 for XXH3 hashing of a 128 byte array. + */ +@State(Scope.Thread) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Fork( + value = 5, + warmups = 3, + jvmArgs = { + "-XX:-TieredCompilation", + "-XX:+UseParallelGC", + "-Xms16g", + "-Xmx16g", + }) +public class XXH3HashBytesBenchmark { + private static final int INPUT_LENGTH_BYTES = 128; + private static final LongHashFunction XXH3_64_HASH_FUNCTION = LongHashFunction.xx3(); + private static final byte[] ZERO_FILLED_128_BYTE_INPUT = new byte[INPUT_LENGTH_BYTES]; + + @Benchmark + public long hashZeroFilledByteArray128Bytes() { + return XXH3_64_HASH_FUNCTION.hashBytes(ZERO_FILLED_128_BYTE_INPUT); + } + + public static void main(String[] args) throws IOException { + org.openjdk.jmh.Main.main(args); + } +}