The full picture
// Auto-vectorization candidate (JIT usually gets this one):
for (int i = 0; i < n; i++) c[i] = a[i] * b[i] + c[i]; // fused multiply-add, 8 lanes
// What quietly breaks it: branches, calls, i%3 strides, float reductions
// (reassociation changes results -> JIT declines), aliasing doubts.
// Vector API — explicit, portable across AVX2/AVX-512/NEON:
static final VectorSpecies<Float> S = FloatVector.SPECIES_PREFERRED;
for (int i = 0; i < S.loopBound(n); i += S.length()) {
var va = FloatVector.fromArray(S, a, i);
var vb = FloatVector.fromArray(S, b, i);
va.mul(vb).add(FloatVector.fromArray(S, c, i)).intoArray(c, i);
}
for (int i = S.loopBound(n); i < n; i++) c[i] += a[i] * b[i]; // tail loop- Where SIMD is already earning for you invisibly:
Arrays.fill/equals/mismatch,String.indexOf, UTF-8 validation, CRC/checksums, JSON parsing (simdjson's whole thesis), compression codecs — the JDK and libraries hand-vectorized the primitives so business code benefits without knowing. - Why float reductions don't auto-vectorize: (a+b)+c ≠ a+(b+c) in IEEE 754 — vectorizing a sum reorders it, so the JIT refuses unless semantics allow; the Vector API makes you choose explicitly (
reduceLanes(ADD)) — a beautiful callback to the floating-point question. - Data layout is the entry fee: SIMD wants contiguous primitive arrays (SoA) — object graphs can't vectorize; this is the same locality argument as caches, and one more motivation for Valhalla.
- Verification: JMH +
-XX:+PrintAssembly/perfasm to see vector registers actually used; 'the JIT probably vectorized it' without looking is the performance version of 'it compiles so it works'.