Interview Prep Zoneby CuriouserLabs

Question 2 of 5 · staff level

What is SIMD? When does the JIT vectorize your Java, and when do you reach for the Vector API?

🎤 Say this first

SIMD = one instruction operating on a whole register of lanes — AVX2 (256-bit) processes 8 ints at once; AVX-512/SVE more — potentially 8–16× on data-parallel loops for free silicon you're already paying for. HotSpot auto-vectorizes simple counted loops over arrays (no data-dependent branches, no cross-iteration dependencies, straight-line arithmetic) — but silently gives up on anything complex, and you get zero feedback. The Vector API (jdk.incubator.vector, Panama) makes it explicit and guaranteed: you write lane-width-agnostic code (SPECIES_PREFERRED), it compiles to real vector instructions or scalar fallback. Reach for it when profiling shows a hot numeric kernel and you need vectorization you can rely on rather than hope for.

The full picture

Hope vs guarantee
// 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'.

🔄 Likely follow-up questions

  • Why does AVX-512 sometimes downclock, and how do you decide it's worth it?
  • What loop shapes defeat auto-vectorization, and how do you restructure them?
  • How do simdjson-style parsers use SIMD on 'branchy' problems?