Multiplicação Vetorial com Instruções SSE e AVX
As extensões SSE e AVX fornecem um conjunto de instruções para a multiplicação de elementos em vetores.
Multiplicação com Resultado Truncado
A principal complexidade da multiplicação vetorial é que o produto de dois números de n bits pode gerar um resultado de até 2n bits. No entanto, a pista de destino no registrador SIMD tem apenas n bits. Para lidar com isso, a maioria das instruções de multiplicação armazena apenas uma parte do resultado (a parte inferior ou a superior).
- pmullw / vpmullw: Multiplica words (16 bits) e armazena os 16 bits inferiores (low word) do resultado.
- pmulhuw / vpmulhuw: Multiplica words sem sinal e armazena os 16 bits superiores (high word) do resultado.
- pmulhw / vpmulhw: Multiplica words com sinal e armazena os 16 bits superiores (high word) do resultado.
- pmulld / vpmulld: Multiplica dwords (32 bits) e armazena os 32 bits inferiores (low dword) do resultado.
- vpmullq: Multiplica qwords (64 bits) e armazena os 64 bits inferiores (low qword) do resultado.
A operação de vpmulld de 128 bits, por exemplo, pode ser visualizada da seguinte forma:
// Multiplica os 4 pares de dwords de 32 bits
Temp0[63:0] := SRC1[31:0] * SRC2[31:0]
Temp1[63:0] := SRC1[63:32] * SRC2[63:32]
Temp2[63:0] := SRC1[95:64] * SRC2[95:64]
Temp3[63:0] := SRC1[127:96] * SRC2[127:96]
// Armazena os 32 bits inferiores de cada resultado temporário no destino
DEST[31:0] := Temp0[31:0]
DEST[63:32] := Temp1[31:0]
DEST[95:64] := Temp2[31:0]
DEST[127:96] := Temp3[31:0]As versões de 256 bits estendem essa lógica para 8 pistas.
A sintaxe geral para essas instruções é a seguinte:
pmullw xmmdest, xmmsrc/mem128
vpmullw xmmdest, xmmsrc, xmm/mem128
vpmullw ymmdest, ymmsrc, ymm/mem256
pmulhuw xmmdest, xmmsrc/mem128
vpmulhuw xmmdest, xmmsrc, xmm/mem128
vpmulhuw ymmdest, ymmsrc, ymm/mem256
pmulhw xmmdest, xmmsrc/mem128
vpmulhw xmmdest, xmmsrc, xmm/mem128
vpmulhw ymmdest, ymmsrc, ymm/mem256
pmulld xmmdest, xmmsrc/mem128
vpmulld xmmdest, xmmsrc, xmm/mem128
vpmulld ymmdest, ymmsrc, ymm/mem256
vpmullq xmmdest, xmmsrc, xmm/mem128
vpmullq ymmdest, ymmsrc, ymm/mem256O exemplo a seguir para Linux demonstra a multiplicação de dwords.
global main
extern printf
section .data
nums0 dd 5, 6, 7, 8
nums1 dd 4, 5, 6, 7
format_str db "%d, %d, %d, %d", 10, 0
section .text
main:
sub rsp, 8
movaps xmm0, [nums0]
movaps xmm1, [nums1]
vpmulld xmm0, xmm0, xmm1 ; XMM0 = XMM0 * XMM1
; Resultado em XMM0: [20, 30, 42, 56]
; Exibe os dados no console
movd esi, xmm0
psrldq xmm0, 4
movd edx, xmm0
psrldq xmm0, 4
movd ecx, xmm0
psrldq xmm0, 4
movd r8d, xmm0
mov rdi, format_str
xor rax, rax
call printf
add rsp, 8
retNeste código, as pistas de 32 bits dos registradores xmm0 e xmm1 são multiplicadas.
Resultado da execução do programa:
$ nasm -f elf64 hello.asm -o hello.o $ gcc -static hello.o -o hello $ ./hello 20, 30, 42, 56
A seguir, a versão análoga do programa para Windows.
global main
extern printf
section .data
nums0 dd 5, 6, 7, 8
nums1 dd 4, 5, 6, 7
format_str db "%d, %d, %d, %d", 10, 0
section .text
main:
sub rsp, 40
movaps xmm0, [rel nums0]
movaps xmm1, [rel nums1]
vpmulld xmm0, xmm0, xmm1 ; XMM0 = XMM0 * XMM1
; Resultado em XMM0: [20, 30, 42, 56]
movd edx, xmm0
psrldq xmm0, 4
movd r8d, xmm0
psrldq xmm0, 4
movd r9d, xmm0
psrldq xmm0, 4
movd eax, xmm0
mov dword [rsp+32], eax
mov rcx, format_str
call printf
add rsp, 40
retMultiplicação com Resultado Completo
Existem instruções específicas que contornam a limitação do tamanho do resultado, armazenando o produto completo em uma pista de tamanho maior.
- pmuldq / vpmuldq: Multiplica os dwords (32 bits) com sinal das pistas 0 e 2, e armazena os resultados completos de 64 bits nas pistas de qword 0 e 1 do registrador de destino.
- pmuludq / vpmuludq: Similar à anterior, mas opera com dwords sem sinal.
A sintaxe dessas instruções é a seguinte:
pmuldq xmmdest, xmmsrc/mem128
vpmuldq xmmdest, xmmsrc1, xmm/mem128
vpmuldq ymmdest, ymmsrc1, ymm/mem256
pmuludq xmmdest, xmmsrc/mem128
vpmuludq xmmdest, xmmsrc1, xmm/mem128
vpmuludq ymmdest, ymmsrc1, ymm/mem256Outra instrução notável é a pclmulqdq (e vpclmulqdq), que realiza multiplicação carry-less de qwords (64 bits) e armazena o resultado de 128 bits.
pclmulqdq xmmdest, xmmsrc/mem128, imm8
vpclmulqdq xmmdest, xmmsrc1, xmmsrc2/mem128, imm8O operando imediato imm8 seleciona quais qwords dos operandos de origem serão multiplicados.
| imm8 | Operação (vpclmulqdq) |
|---|---|
| 0x00 | dest = src1[0-63] * src2[0-63] |
| 0x01 | dest = src1[64-127] * src2[0-63] |
| 0x10 | dest = src1[0-63] * src2[64-127] |
| 0x11 | dest = src1[64-127] * src2[64-127] |
O exemplo a seguir para Linux demonstra a multiplicação de qwords de 64 bits.
global main
extern printf
section .data
nums0 dq 5, 6
nums1 dq 3, 4
format_str db "%ld", 10, 0
section .text
main:
sub rsp, 8
movaps xmm0, [nums0]
movaps xmm1, [nums1]
vpclmulqdq xmm0, xmm0, xmm1, 0x00
; Resultado em XMM0[0:63]: 15
; Exibe os dados no console
movq rsi, xmm0
mov rdi, format_str
xor rax, rax
call printf
add rsp, 8
retResumo
- A multiplicação SIMD precisa lidar com o fato de que o produto de
nbits pode ter2nbits. - Instruções como
pmullwepmulldarmazenam a parte inferior do resultado, descartando a parte superior. - Instruções como
pmulhwepmulhuwarmazenam a parte superior do resultado, descartando a inferior. - Instruções especializadas como
pmuldqepclmulqdqforam criadas para armazenar o resultado completo da multiplicação em pistas maiores.