Skip to content

Commit 02331d9

Browse files
authored
Merge pull request #117 from OpenHFT/feature/multiplyHigh
Add JMH benchmark proving direct `Math.multiplyHigh` path outperforms `invokeExact`
2 parents 59b706d + b3cc18d commit 02331d9

3 files changed

Lines changed: 73 additions & 27 deletions

File tree

pom.xml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
<maven.bundle.plugin.version>5.1.1</maven.bundle.plugin.version>
3939

4040
<doclint>all,-missing</doclint>
41+
<jmh.version>1.37</jmh.version>
42+
<sonar.organization>openhft</sonar.organization>
43+
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
4144
</properties>
4245

4346
<dependencies>
@@ -74,6 +77,20 @@
7477
<scope>test</scope>
7578
</dependency>
7679

80+
<dependency>
81+
<groupId>org.openjdk.jmh</groupId>
82+
<artifactId>jmh-core</artifactId>
83+
<version>${jmh.version}</version>
84+
<scope>test</scope>
85+
</dependency>
86+
87+
<dependency>
88+
<groupId>org.openjdk.jmh</groupId>
89+
<artifactId>jmh-generator-annprocess</artifactId>
90+
<version>${jmh.version}</version>
91+
<scope>test</scope>
92+
</dependency>
93+
7794
</dependencies>
7895

7996
<build>
@@ -273,7 +290,6 @@
273290
</execution>
274291
</executions>
275292
</plugin>
276-
277293
</plugins>
278294
</build>
279295

src/main/java/net/openhft/hashing/Maths.java

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,26 @@
55

66
import org.jetbrains.annotations.NotNull;
77

8-
import java.lang.invoke.MethodHandle;
9-
import java.lang.invoke.MethodHandles;
10-
import java.lang.reflect.Method;
11-
8+
@SuppressWarnings("Since15")
129
class Maths {
1310
@NotNull
1411
private static final Maths INSTANCE;
1512

1613
static {
17-
Maths maths = null;
14+
boolean hasMultiplyHigh = true;
1815
try {
19-
Method multiplyHigh = Math.class.getDeclaredMethod("multiplyHigh", int.class, int.class);
20-
MethodHandle multiplyHighMH = MethodHandles.lookup().unreflect(multiplyHigh);
21-
maths = new MathsJDK9(multiplyHighMH);
16+
// stubs in src/main/java-stub are used to allow this to compile in Java 8+
17+
Math.multiplyHigh(0, 0);
2218
} catch (final Throwable ignore) {
23-
maths = new Maths();
19+
hasMultiplyHigh = false;
2420
}
25-
INSTANCE = maths;
21+
INSTANCE = hasMultiplyHigh ? new MathsJDK9() : new Maths();
2622
}
2723

2824
public static long unsignedLongMulXorFold(final long lhs, final long rhs) {
2925
return INSTANCE.unsignedLongMulXorFoldImp(lhs, rhs);
3026
}
27+
3128
public static long unsignedLongMulHigh(final long lhs, final long rhs) {
3229
return INSTANCE.unsignedLongMulHighImp(lhs, rhs);
3330
}
@@ -70,31 +67,19 @@ long unsignedLongMulHighImp(final long lhs, final long rhs) {
7067
}
7168
}
7269

70+
@SuppressWarnings("Since15")
7371
class MathsJDK9 extends Maths {
74-
private final MethodHandle multiplyHighMH;
75-
76-
public MathsJDK9(MethodHandle multiplyHighMH) {
77-
this.multiplyHighMH = multiplyHighMH;
78-
}
79-
8072
// Math.multiplyHigh() is intrinsified from JDK 10. But JDK 9 is out of life, we always prefer
8173
// this version to the scalar one.
8274
@Override
8375
long unsignedLongMulXorFoldImp(final long lhs, final long rhs) {
84-
final long upper = invokeExact(lhs, rhs) + ((lhs >> 63) & rhs) + ((rhs >> 63) & lhs);
76+
final long upper = Math.multiplyHigh(lhs, rhs) + ((lhs >> 63) & rhs) + ((rhs >> 63) & lhs);
8577
final long lower = lhs * rhs;
8678
return lower ^ upper;
8779
}
80+
8881
@Override
8982
long unsignedLongMulHighImp(final long lhs, final long rhs) {
90-
return invokeExact(lhs, rhs) + ((lhs >> 63) & rhs) + ((rhs >> 63) & lhs);
91-
}
92-
93-
private long invokeExact(long lhs, long rhs) {
94-
try {
95-
return (long) multiplyHighMH.invokeExact(lhs, rhs);
96-
} catch (Throwable e) {
97-
throw new AssertionError(e);
98-
}
83+
return Math.multiplyHigh(lhs, rhs) + ((lhs >> 63) & rhs) + ((rhs >> 63) & lhs);
9984
}
10085
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2013-2025 chronicle.software; SPDX-License-Identifier: Apache-2.0
3+
*/
4+
package net.openhft.hashing;
5+
6+
import org.openjdk.jmh.annotations.Benchmark;
7+
import org.openjdk.jmh.annotations.BenchmarkMode;
8+
import org.openjdk.jmh.annotations.Fork;
9+
import org.openjdk.jmh.annotations.Mode;
10+
import org.openjdk.jmh.annotations.OutputTimeUnit;
11+
import org.openjdk.jmh.annotations.Scope;
12+
import org.openjdk.jmh.annotations.State;
13+
14+
import java.io.IOException;
15+
import java.util.concurrent.TimeUnit;
16+
17+
/**
18+
* Reproduces issue #101 for XXH3 hashing of a 128 byte array.
19+
*/
20+
@State(Scope.Thread)
21+
@BenchmarkMode(Mode.Throughput)
22+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
23+
@Fork(
24+
value = 5,
25+
warmups = 3,
26+
jvmArgs = {
27+
"-XX:-TieredCompilation",
28+
"-XX:+UseParallelGC",
29+
"-Xms16g",
30+
"-Xmx16g",
31+
})
32+
public class XXH3HashBytesBenchmark {
33+
private static final int INPUT_LENGTH_BYTES = 128;
34+
private static final LongHashFunction XXH3_64_HASH_FUNCTION = LongHashFunction.xx3();
35+
private static final byte[] ZERO_FILLED_128_BYTE_INPUT = new byte[INPUT_LENGTH_BYTES];
36+
37+
@Benchmark
38+
public long hashZeroFilledByteArray128Bytes() {
39+
return XXH3_64_HASH_FUNCTION.hashBytes(ZERO_FILLED_128_BYTE_INPUT);
40+
}
41+
42+
public static void main(String[] args) throws IOException {
43+
org.openjdk.jmh.Main.main(args);
44+
}
45+
}

0 commit comments

Comments
 (0)