Die Anforderung an moderne Java-Anwendungen wächst stetig: Ob Machine Learning, Bildverarbeitung oder wissenschaftliche Simulationen — immer häufiger müssen große Datenmengen in kürzester Zeit verarbeitet werden. Während Java-Entwickler traditionell auf native Bibliotheken oder JNI zurückgreifen mussten, um SIMD-Instruktionen (Single Instruction, Multiple Data) der CPU zu nutzen, bietet die Vector API seit Java 16 eine plattformunabhängige Lösung direkt im JDK.

Dieser Artikel zeigt, wie du mit der Vector API numerische Berechnungen auf das nächste Leistungsniveau hebst — ohne eine Zeile C-Code oder JNI.

Was ist die Vector API?

Die Vector API (JEP 508 im 10. Inkubator in Java 25) erlaubt es, Vektoroperationen direkt in Java zu schreiben. Der JIT-Compiler (C2) übersetzt diese plattformspezifisch in AVX2- (x86-64), AVX-512- (x86-64) oder NEON-Instruktionen (AArch64). Anders als die Auto-Vektorisierung des Compilers, die oft scheitert, gibt dir die Vector API deterministisch Kontrolle über die SIMD-Nutzung.

Wichtig: Die API befindet sich noch im Inkubator-Stadium (Modul jdk.incubator.vector). Sie wird voraussichtlich gemeinsam mit Project Valhallas Value Classes finalisiert.

Hello World der Vector API: Vektoraddition

Das einfachste Beispiel sind zwei int-Arrays, die elementweise addiert werden:

import jdk.incubator.vector.IntVector;
import jdk.incubator.vector.VectorSpecies;

public class VectorAdd {
    static final VectorSpecies<Integer> SPECIES = IntVector.SPECIES_PREFERRED;

    public static int[] add(int[] a, int[] b) {
        int[] result = new int[a.length];
        int i = 0;
        int bound = SPECIES.loopBound(a.length);

        for (; i < bound; i += SPECIES.length()) {
            var va = IntVector.fromArray(SPECIES, a, i);
            var vb = IntVector.fromArray(SPECIES, b, i);
            va.add(vb).intoArray(result, i);
        }
        for (; i < a.length; i++) {
            result[i] = a[i] + b[i];
        }
        return result;
    }
}
Code-Sprache: PHP (php)

Die Klasse IntVector lädt eine feste Anzahl von int-Werten (z.B. 8 auf AVX2, 16 auf AVX-512) in ein Vektorregister, addiert sie in einer einzigen CPU-Instruktion und schreibt das Ergebnis zurück. Der VectorSpecies<Integer> definiert dabei Elementtyp und Vektorbreite. SPECIES_PREFERRED wählt automatisch die optimale Breite für die aktuelle Plattform.

Der Rest des Schleifenkörpers (i < a.length) behandelt die übrig gebliebenen Elemente, die nicht in einen vollen Vektor passen — der sogenannte „Scalar Tail“.

Skalarprodukt mit Reduce-Lanes

Etwas spannender wird es beim Skalarprodukt, bei dem das Ergebnis aller Einzelprodukte auf einen Wert reduziert wird:

import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.VectorOperators;

static final VectorSpecies<Float> FSPECIES = FloatVector.SPECIES_PREFERRED;

public static float dotProduct(float[] a, float[] b) {
    float sum = 0.0f;
    int i = 0;
    int bound = FSPECIES.loopBound(a.length);

    for (; i < bound; i += FSPECIES.length()) {
        var va = FloatVector.fromArray(FSPECIES, a, i);
        var vb = FloatVector.fromArray(FSPECIES, b, i);
        sum += va.mul(vb).reduceLanes(VectorOperators.ADD);
    }
    for (; i < a.length; i++) {
        sum += a[i] * b[i];
    }
    return sum;
}
Code-Sprache: JavaScript (javascript)

mul(vb) multipliziert die Elemente beider Vektoren (lane-wise), und reduceLanes(ADD) summiert das Ergebnisvektor-Register in einen einzelnen float-Wert. Diese Kombination mappt ideal auf moderne FMA-Instruktionen (Fused Multiply-Add).

Matrix-Multiplikation

Die wahre Stärke entfaltet die API bei mehrdimensionalen Operationen. Diese Matrix-Multiplikation nutzt fma() (Fused Multiply-Add) für optimale Genauigkeit und Performance:

import jdk.incubator.vector.DoubleVector;

static final VectorSpecies<Double> DSPECIES = DoubleVector.SPECIES_PREFERRED;

public static double[][] matmul(double[][] A, double[][] B) {
    int m = A.length, n = B[0].length, inner = B.length;
    double[][] C = new double[m][n];

    for (int i = 0; i < m; i++) {
        for (int k = 0; k < inner; k++) {
            double aik = A[i][k];
            int j = 0;
            int bound = DSPECIES.loopBound(n);
            for (; j < bound; j += DSPECIES.length()) {
                var vb = DoubleVector.fromArray(DSPECIES, B[k], j);
                var vc = DoubleVector.fromArray(DSPECIES, C[i], j);
                vb.fma(aik, vc).intoArray(C[i], j);
            }
            for (; j < n; j++) {
                C[i][j] += aik * B[k][j];
            }
        }
    }
    return C;
}
Code-Sprache: PHP (php)

Die Methode fma(scale, addend) berechnet vb * aik + vc in einer einzigen Hardware-Instruktion — doppelt so schnell wie getrenntes Mul+Add.

Transzendente Funktionen

Die FloatVector– und DoubleVector-Klassen bieten auch vektorisierte Varianten von sin()cos()exp()log()sqrt() und vielen weiteren — unterstützt auf x64 (Linux/Windows) durch Intels SVML, auf ARM und RISC-V (Linux/macOS) durch SLEEF:

public static float[] vectorExp(float[] arr) {
    float[] result = new float[arr.length];
    int i = 0, bound = FSPECIES.loopBound(arr.length);
    for (; i < bound; i += FSPECIES.length()) {
        FloatVector.fromArray(FSPECIES, arr, i)
                   .exp()
                   .intoArray(result, i);
    }
    for (; i < arr.length; i++) result[i] = (float) Math.exp(arr[i]);
    return result;
}
Code-Sprache: JavaScript (javascript)

Build-Setup (Maven & Gradle)

Da die Vector API ein Inkubator-Modul ist, musst du sie explizit aktivieren:

Maven:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <compilerArgs>
            <arg>--add-modules</arg>
            <arg>jdk.incubator.vector</arg>
        </compilerArgs>
    </configuration>
</plugin>
Code-Sprache: HTML, XML (xml)

Gradle:

tasks.withType(JavaCompile) {
    options.compilerArgs += ['--add-modules', 'jdk.incubator.vector']
}
Code-Sprache: JavaScript (javascript)

Kommandozeile:

javac --add-modules jdk.incubator.vector MeinProgramm.java
java --add-modules jdk.incubator.vector MeinProgramm
Code-Sprache: CSS (css)

Performance-Einordnung

Die Vector API lohnt sich besonders bei großen Arrays mit vielen Iterationen. In eigenen Benchmarks erreicht die vektorisierte Vektoraddition auf einem Intel Core i7 mit AVX2 etwa das 3- bis 4-fache der skalaren Variante. Die Gewinne steigen mit AVX-512 nochmals signifikant.

Entscheidend ist, dass der VectorSpecies als static final Feld deklariert wird — nur dann kann C2 ihn als Konstante betrachten und optimale Instruktionen generieren. Ebenfalls wichtig: C2-Only. Die Vector API wird nur von der höchsten JIT-Stufe intrinsifiziert.

Fazit

Die Vector API bringt SIMD-Performance in idiomatisches Java — ohne native Abhängigkeiten, JNI oder plattformspezifischen Code. Sie eignet sich für alle rechenintensiven Anwendungen von Bildverarbeitung bis zu ML-Inferenz. Die Inkubator-Phase sollte kein Hindernis sein: In Java 25 ist es bereits der 10. Inkubator, die API ist stabil und produktionsreif.

Für alle, die numerisch anspruchsvolle Java-Anwendungen schreiben: Gebt der Vector API eine Chance — eure CPU wird es euch danken.