Estevão Dias

3 min de leitura

SIMD e a Vector API do Java

Estevão Dias

Single Instruction, Multiple Data, ou SIMD. A Vector API, que chega ao seu décimo primeiro ciclo de incubação no Java 26, é sobre isso.

Hoje, as CPUs possuem registradores vetoriais capazes de armazenar e operar sobre vários valores de uma vez. Esses valores são divididos em lanes. Assim, se tenho dois vetores de oito posições e quero somá-los, posso realizar as oito somas usando uma única instrução SIMD, em vez de executar oito instruções escalares.

O exemplo que todo mundo tá usando é esse:

int[] a = {1, 2, 3, 4, 5, 6, 7, 8};
int[] b = {10, 20, 30, 40, 50, 60, 70, 80};
int[] result = new int[8];
for (int i = 0; i < 8; i++) {
result[i] = a[i] + b[i];
}
1 + 10
2 + 20
3 + 30
...
8 + 80

Com a Vector API, podemos expressar a mesma operação como uma operação vetorial, assim:

IntVector va = IntVector.fromArray(SPECIES, a, 0);
IntVector vb = IntVector.fromArray(SPECIES, b, 0);
IntVector result = va.add(vb);
result.intoArray(output, 0);
va: [1, 2, 3, 4, 5, 6, 7, 8]
vb: [10, 20, 30, 40, 50, 60, 70, 80]
...
[11, 22, 33, 44, 55, 66, 77, 88]

Não sei você, eu quando vejo isso lembro a frase célebre do filósofo Mr Catra

Dar-se-á início, doravante, ao auspicioso e desmedido colóquio das licenciosidades.

Retomando, quando eu vi isso pensei “a soma acontece em paralelo”. Mas não é isso. Não são criadas oito threads. Nem oito tarefas concorrentes. Esqueça threads. SIMD é uma característica da arquitetura da CPU. Quer dizer, uma única instrução pode operar sobre múltiplos elementos de dados.

A CPU possui registradores, que são pequenas áreas de armazenamento extremamente rápidas localizadas dentro do processador. Durante muito tempo, as instruções tradicionais operavam essencialmente sobre um valor escalar por vez. CPUs modernas, sempre elas, possuem registradores vetoriais capazes de armazenar vários valores simultaneamente. É aí que entram as lanes.

É extenso, mas pra concluir aqui, existem diferentes quantidades de lanes, dependendo do tipo de dado e da largura do vetor. É o que SPECIES representa. IntVector.SPECIES_256 considera 256/32, 8 lanes de 32 bits. Por que 32? É um IntVector. Se fosse DoubleVector, seria 256/64, e 4 lanes. O tamanho e o suporte efetivo dependem da CPU, mas acho que pra uma introdução até aqui tá de boa.

JIT compiler da JVM já é capaz de realizar auto-vectorization em certas situações. Vector API é mais sobre o programador poder explicitar a intenção de executar uma operação vetorial, sem precisar escrever código específico para uma determinada instrução SIMD ou arquitetura de CPU.

Isso abre muitas possibilidades e é particularmente interessante quando a aplicação realiza grandes quantidades de operações independentes sobre dados. Processamento de áudio e vídeo, simulações e computação científica, processamento de imagens, criptografia, álgebra linear e determinadas operações utilizadas em machine learning são exemplos de workloads que podem se beneficiar desse tipo de otimização.