1
0

Complete runnable Vector API (JEP 537) demo

SIMD kernels (square-and-add, masked filter, brightness clamp, FP reduction ordering), an exploratory timing harness, JMH benchmarks with Maven and Maven-free builds, run scripts, README and the captured results with their exact environment.
This commit was merged in pull request #1.
This commit is contained in:
2026-07-26 11:50:15 +00:00
parent e4695ab334
commit 780848e102
18 changed files with 824 additions and 2 deletions

85
jmh/pom.xml Normal file
View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ankurm</groupId>
<artifactId>vector-api-jmh</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Vector API JMH benchmarks</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>21</maven.compiler.release>
<jmh.version>1.37</jmh.version>
<uberjar.name>benchmarks</uberjar.name>
</properties>
<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<compilerArgs>
<arg>--add-modules</arg>
<arg>jdk.incubator.vector</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<finalName>${uberjar.name}</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

15
jmh/run-without-maven.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Same benchmarks without Maven: fetch the four jars, compile with the JMH
# annotation processor, run org.openjdk.jmh.Main directly.
set -euo pipefail
cd "$(dirname "$0")"
mkdir -p lib out
for u in org/openjdk/jmh/jmh-core/1.37/jmh-core-1.37.jar \
org/openjdk/jmh/jmh-generator-annprocess/1.37/jmh-generator-annprocess-1.37.jar \
net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4.jar \
org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1.jar; do
[ -f "lib/$(basename "$u")" ] || curl -sSL -o "lib/$(basename "$u")" "https://repo1.maven.org/maven2/$u"
done
CP=$(ls lib/*.jar | tr '\n' ':')
javac --add-modules jdk.incubator.vector -cp "$CP" -proc:full -d out $(find src -name '*.java')
java --add-modules jdk.incubator.vector -cp "out:$CP" org.openjdk.jmh.Main "$@"

8
jmh/run.sh Normal file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
# Build and run the JMH benchmarks (the numbers worth defending).
# ./run.sh -> all benchmarks
# ./run.sh MaskedBench -> one class
set -euo pipefail
cd "$(dirname "$0")"
mvn -q -B clean package
java --add-modules jdk.incubator.vector -jar target/benchmarks.jar "$@"

View File

@@ -0,0 +1,55 @@
package com.ankurm.vectorapi;
import jdk.incubator.vector.*;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/** JMH version of the image brightness + clamp kernel. */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(1)
public class BrightnessBench {
static final VectorSpecies<Float> SP = FloatVector.SPECIES_PREFERRED;
float[] in, out;
float gain = 1.4f, bias = 12f;
@Setup
public void setup() {
in = new float[8192]; out = new float[8192];
Random r = new Random(11);
for (int i = 0; i < in.length; i++) in[i] = r.nextFloat() * 300f - 20f;
}
@Benchmark
public void scalarClamp(Blackhole bh) {
for (int i = 0; i < in.length; i++) {
float v = in[i] * gain + bias;
if (v < 0f) v = 0f; else if (v > 255f) v = 255f;
out[i] = v;
}
bh.consume(out);
}
@Benchmark
public void vectorClamp(Blackhole bh) {
var vg = FloatVector.broadcast(SP, gain);
var vb = FloatVector.broadcast(SP, bias);
var lo = FloatVector.zero(SP);
var hi = FloatVector.broadcast(SP, 255f);
int i = 0, bound = SP.loopBound(in.length);
for (; i < bound; i += SP.length()) {
FloatVector.fromArray(SP, in, i).fma(vg, vb).max(lo).min(hi).intoArray(out, i);
}
for (; i < in.length; i++) {
float v = in[i] * gain + bias;
if (v < 0f) v = 0f; else if (v > 255f) v = 255f;
out[i] = v;
}
bh.consume(out);
}
}

View File

@@ -0,0 +1,47 @@
package com.ankurm.vectorapi;
import jdk.incubator.vector.*;
import org.openjdk.jmh.annotations.*;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/** JMH version of the masked filter-and-sum: the headline speedup case. */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(1)
public class MaskedBench {
static final VectorSpecies<Float> SP = FloatVector.SPECIES_PREFERRED;
static final float THRESH = 0.5f;
float[] a;
@Setup
public void setup() {
a = new float[8192];
Random r = new Random(7);
for (int i = 0; i < a.length; i++) a[i] = r.nextFloat();
}
// Both methods return their result, so JMH consumes it and nothing is dead.
@Benchmark
public float scalarBranch() {
float sum = 0;
for (int i = 0; i < a.length; i++) if (a[i] > THRESH) sum += a[i];
return sum;
}
@Benchmark
public float vectorMasked() {
FloatVector acc = FloatVector.zero(SP);
int i = 0, bound = SP.loopBound(a.length);
for (; i < bound; i += SP.length()) {
var v = FloatVector.fromArray(SP, a, i);
acc = acc.add(v, v.compare(VectorOperators.GT, THRESH));
}
float sum = acc.reduceLanes(VectorOperators.ADD);
for (; i < a.length; i++) if (a[i] > THRESH) sum += a[i];
return sum;
}
}

View File

@@ -0,0 +1,63 @@
package com.ankurm.vectorapi;
import jdk.incubator.vector.*;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(1)
public class VectorBench {
static final VectorSpecies<Float> SP = FloatVector.SPECIES_PREFERRED;
float[] a, b, c;
@Setup
public void setup() {
int n = 8192;
a = new float[n]; b = new float[n]; c = new float[n];
Random r = new Random(42);
for (int i = 0; i < n; i++) { a[i] = r.nextFloat(); b[i] = r.nextFloat(); }
}
// The result array is written, then handed to JMH's Blackhole so neither the
// stores nor the arithmetic can be eliminated as dead code.
@Benchmark
public void scalar(Blackhole bh) {
for (int i = 0; i < a.length; i++) c[i] = -(a[i]*a[i] + b[i]*b[i]);
bh.consume(c);
}
@Benchmark
public void vector(Blackhole bh) {
int i = 0, bound = SP.loopBound(a.length);
for (; i < bound; i += SP.length()) {
var va = FloatVector.fromArray(SP, a, i);
var vb = FloatVector.fromArray(SP, b, i);
va.mul(va).add(vb.mul(vb)).neg().intoArray(c, i);
}
for (; i < a.length; i++) c[i] = -(a[i]*a[i] + b[i]*b[i]);
bh.consume(c);
}
// Same kernel, but returning a value derived from the output. Returned values
// are consumed by JMH automatically. Note what this costs: the checksum loop is
// itself measured, which is why this variant reports ~9x the time of vector().
@Benchmark
public float vectorChecksum() {
int i = 0, bound = SP.loopBound(a.length);
for (; i < bound; i += SP.length()) {
var va = FloatVector.fromArray(SP, a, i);
var vb = FloatVector.fromArray(SP, b, i);
va.mul(va).add(vb.mul(vb)).neg().intoArray(c, i);
}
for (; i < a.length; i++) c[i] = -(a[i]*a[i] + b[i]*b[i]);
float sum = 0;
for (float v : c) sum += v;
return sum;
}
}