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:
123
RESULTS.md
Normal file
123
RESULTS.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Captured results
|
||||
|
||||
Everything below was produced by the code in this repository. Your numbers will
|
||||
differ — that is the point of shipping the code rather than only the table.
|
||||
|
||||
## Environment
|
||||
|
||||
```
|
||||
JDK : OpenJDK 21.0.2+13-58 (HotSpot 64-Bit Server VM, mixed mode)
|
||||
OS : Ubuntu 22.04.5 LTS, Linux 6.8.0-124-generic, x86_64
|
||||
CPU : AMD Ryzen 5 5600U (Zen 3), 2 vCPUs (1 core / 2 threads) exposed
|
||||
Hypervisor : Microsoft Hyper-V, full virtualization
|
||||
ISA : AVX2 + FMA; no AVX-512 -> FloatVector.SPECIES_PREFERRED = 8 lanes, 256-bit
|
||||
Caches : L1d 32 KiB, L2 512 KiB, L3 16 MiB (as reported to the guest)
|
||||
JVM flags : --add-modules jdk.incubator.vector (plus -XX:-UseSuperWord where noted)
|
||||
Threads : single-threaded throughout
|
||||
Data : 8192 floats = 32 KiB per array, cache-resident
|
||||
Frequency : not controllable inside the guest (no cpufreq governor exposed)
|
||||
```
|
||||
|
||||
The last line matters: on a virtualized, frequency-scaled machine, absolute
|
||||
nanoseconds are soft. Ratios measured back-to-back in the same JVM are far more
|
||||
stable than the absolute numbers, and the JMH table is more trustworthy than the
|
||||
exploratory one.
|
||||
|
||||
## 1. Exploratory timings — `timings/run.sh`
|
||||
|
||||
Harness: 50,000 warmup calls, then 15 rounds of 5,000 calls; median reported.
|
||||
These are teaching experiments, not statistically rigorous benchmarks.
|
||||
|
||||
```
|
||||
--- kernel 1: c[i] = -(a[i]^2 + b[i]^2), 8192 floats ---
|
||||
scalar for-loop median 832.0 ns/call best 819.6 ns/call
|
||||
Vector API median 1279.9 ns/call best 1207.8 ns/call
|
||||
vector advantage 0.65x
|
||||
|
||||
--- kernel 2: masked filter-and-sum, 8192 floats ---
|
||||
scalar sum = 3084.1404
|
||||
vector sum = 3084.1448 (same value, different rounding)
|
||||
scalar (branch) median 5021.9 ns/call best 4926.9 ns/call
|
||||
Vector API (masked) median 1029.9 ns/call best 1021.4 ns/call
|
||||
vector advantage 4.88x
|
||||
|
||||
--- kernel 3: image brightness + clamp, 8192 pixels ---
|
||||
scalar (branchy clamp) median 6881.6 ns/call best 6839.2 ns/call
|
||||
Vector API (max/min) median 1243.8 ns/call best 1222.6 ns/call
|
||||
vector advantage 5.53x
|
||||
|
||||
--- floating-point reduction ordering ---
|
||||
scalar (left to right) : 4103.688965
|
||||
vector (lanes, then reduce): 4103.681641
|
||||
```
|
||||
|
||||
## 2. Same experiments with auto-vectorization off — `timings/run.sh -XX:-UseSuperWord`
|
||||
|
||||
```
|
||||
--- kernel 1 --- scalar 3427.0 ns/call vector 1230.1 ns/call -> 2.79x
|
||||
--- kernel 2 --- scalar 5040.4 ns/call vector 1031.3 ns/call -> 4.89x
|
||||
--- kernel 3 --- scalar 6883.9 ns/call vector 1364.1 ns/call -> 5.05x
|
||||
```
|
||||
|
||||
Read the two runs together — that comparison is the whole experiment:
|
||||
|
||||
| Scalar loop | SuperWord ON | SuperWord OFF | Conclusion |
|
||||
|---|---|---|---|
|
||||
| kernel 1 (straight-line math) | 832 ns | 3427 ns | C2 **was** vectorizing it; disabling SuperWord costs 4.1x |
|
||||
| kernel 2 (data-dependent branch) | 5022 ns | 5040 ns | unchanged, so C2 was **not** vectorizing it |
|
||||
| kernel 3 (branchy clamp) | 6882 ns | 6884 ns | unchanged, so C2 was **not** vectorizing it |
|
||||
|
||||
The Vector API versions are essentially unmoved by the flag in all three cases,
|
||||
which is the property the API is actually selling.
|
||||
|
||||
Run-to-run spread on this machine was a few percent for kernels 1 and 2, and up
|
||||
to ~13% for kernel 3 (5.05x–6.28x observed across runs) — another reminder to
|
||||
quote JMH rather than a stopwatch.
|
||||
|
||||
## 3. JMH — `jmh/run.sh`
|
||||
|
||||
JMH 1.37, 1 fork, 3 warmup iterations x 1 s, 5 measurement iterations x 1 s,
|
||||
single thread, average time per operation.
|
||||
|
||||
```
|
||||
Benchmark Mode Cnt Score Error Units
|
||||
VectorBench.scalar avgt 5 838.631 ± 106.657 ns/op
|
||||
VectorBench.vector avgt 5 814.531 ± 10.766 ns/op
|
||||
VectorBench.vectorChecksum avgt 5 7409.993 ± 224.753 ns/op
|
||||
MaskedBench.scalarBranch avgt 5 5189.874 ± 250.597 ns/op
|
||||
MaskedBench.vectorMasked avgt 5 1036.419 ± 27.726 ns/op
|
||||
BrightnessBench.scalarClamp avgt 5 6510.091 ± 302.638 ns/op
|
||||
BrightnessBench.vectorClamp avgt 5 1133.073 ± 17.763 ns/op
|
||||
```
|
||||
|
||||
Three readings:
|
||||
|
||||
1. **Square-and-add: no measurable difference.** 838.6 ± 106.7 vs 814.5 ± 10.8 —
|
||||
the error bars overlap. On a loop C2 already vectorizes, hand-writing the
|
||||
vector loop bought nothing measurable here. (The exploratory harness made the
|
||||
vector version look ~35% slower; that gap is harness overhead, not the kernel.
|
||||
This is exactly why the JMH number is the one to quote.)
|
||||
2. **Masked filter: 5.0x** (5189.9 / 1036.4), with tight error bars on the vector side.
|
||||
3. **Brightness clamp: 5.7x** (6510.1 / 1133.1).
|
||||
|
||||
`vectorChecksum` is a deliberate cautionary example: returning a checksum does
|
||||
prevent dead-code elimination, but the checksum loop is then *inside* the
|
||||
measurement, and the benchmark reports ~9x the time of the kernel it was meant to
|
||||
measure. Consuming the output array through a `Blackhole` keeps the measurement
|
||||
honest without adding work.
|
||||
|
||||
## What is not reproduced here
|
||||
|
||||
The article shows an excerpt of the AVX2 machine code C2 emits for the
|
||||
square-and-add loop. Reproducing that needs an `hsdis` disassembler plugin
|
||||
installed next to your JDK:
|
||||
|
||||
```
|
||||
java --add-modules jdk.incubator.vector \
|
||||
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly \
|
||||
-XX:CompileCommand=print,com.ankurm.vectorapi.SquareAdd::vectorComputation \
|
||||
-cp out com.ankurm.vectorapi.Main
|
||||
```
|
||||
|
||||
Without `hsdis` the JVM prints `Loading hsdis library failed` and falls back to a
|
||||
hex dump.
|
||||
Reference in New Issue
Block a user