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:
85
jmh/pom.xml
Normal file
85
jmh/pom.xml
Normal 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
15
jmh/run-without-maven.sh
Normal 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
8
jmh/run.sh
Normal 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 "$@"
|
||||
55
jmh/src/main/java/com/ankurm/vectorapi/BrightnessBench.java
Normal file
55
jmh/src/main/java/com/ankurm/vectorapi/BrightnessBench.java
Normal 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);
|
||||
}
|
||||
}
|
||||
47
jmh/src/main/java/com/ankurm/vectorapi/MaskedBench.java
Normal file
47
jmh/src/main/java/com/ankurm/vectorapi/MaskedBench.java
Normal 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;
|
||||
}
|
||||
}
|
||||
63
jmh/src/main/java/com/ankurm/vectorapi/VectorBench.java
Normal file
63
jmh/src/main/java/com/ankurm/vectorapi/VectorBench.java
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user