Instruções intrínsecas
Aprendendo sobre as instruções intrínsecas na arquitetura x86-64
As instruções intrínsecas é um recurso originalmente fornecido pelo compilador Intel C/C++ mas que também é implementado pelo GCC. Se tratam basicamente de tipos especiais e funções que são expandidas inline para alguma instrução do processador, ou seja, é basicamente uma alternativa mais prática e legível do que usar inline Assembly para tudo.
Usando instruções intrínsecas é possível obter o mesmo resultado de usar inline Assembly com a diferença de ter a sintaxe amigável de uma chamada de função.
Para usar instruções intrínsecas é necessário incluir o header <immintrin.h> onde ele declara as funções e os tipos.
Para entender apropriadamente as operações e tipos indicados aqui, sugiro que já tenha lido o tópico sobre SSE.
Tipos de dados
Os tipos de dados na tabela abaixo servem para indicar como os valores usados na instrução intrínseca serão armazenados.
Tipo | Descrição |
| Tipo usado para representar o conteúdo de um registrador MMX. Pode armazenar 8 valores 8-bit, 4 valores de 16-bit, 2 valores de 32-bit ou 1 valor de 64-bit. |
| Representa o conteúdo de um registrador SSE. Pode armazenar 4 valores floating-point de 32-bit. |
| Também um registrador SSE porém armazenando 2 floating-point de 64-bit. |
| Registrador SSE que pode armazenar 16 valores inteiros de 8-bit, 8 valores inteiros de 16-bit, 4 valores inteiros de 32-bit ou 2 valores inteiros de 64-bit. |
| Representa o conteúdo de um registrador YMM usado pela tecnologia AVX. Pode armazenar 8 valores floating-point de 32-bit. |
| Registrador YMM que pode armazenar 4 floating-point de 64-bit. |
| Registrador YMM que pode armazenar 32 valores inteiros de 8-bit, 16 valores inteiros de 16-bit, 8 valores inteiros de 32-bit ou 4 valores inteiros de 64-bit. |
| Representa o conteúdo de um registrador ZMM usado pela tecnologia AVX-512. Pode armazenar 16 valores floating-point de 32-bit. |
| Registrador ZMM que pode armazenar 8 valores floating-point de 64-bit. |
| Registrador ZMM que pode armazenar 64 valores inteiros de 8-bit, 32 inteiros de 16-bit, 16 inteiros de 32-bit ou 8 inteiros de 64-bit. |
Nomenclatura
A maioria das instruções intrínsecas (SIMD) seguem a seguinte convenção de notação:
Onde <operação> é a operação que será executada com os dados. O <sufixo> indica o tipo de dado na operação. A primeira ou as duas primeiras letras do sufixo indicam se o dado é packed (p), extended packed (ep) ou escalar (s). Os demais caracteres do sufixo indicam o tipo de dado, como mostra a tabela abaixo:
Sufixo | Tipo |
| single-precision floating-point (float de 32-bit) |
| double-precision floating-point (double de 64-bit) |
| Inteiro sinalizado de 128-bit. |
| Inteiro sinalizado de 64-bit. |
| Inteiro não-sinalizado de 64-bit. |
| Inteiro sinalizado de 32-bit. |
| Inteiro não-sinalizado de 32-bit. |
| Inteiro sinalizado de 16-bit. |
| Inteiro não-sinalizado de 16-bit. |
| Inteiro sinalizado de 8-bit. |
| Inteiro não-sinalizado de 8-bit. |
Exemplo:
Instruções
Abaixo irei listar apenas algumas instruções intrínsecas, em sua maioria relacionadas à tecnologia SSE. Para ver a lista completa sugiro que consulte a referência oficial da Intel no link abaixo:
Algumas instruções intrínsecas não são compiladas para uma só instrução mas sim uma sequência de várias delas.
Operações load, store e extract
Tecnologia | Protótipo | Instrução |
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE |
|
|
SSE |
| Sequência |
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE |
|
|
SSE4.1 |
|
|
SSE |
|
|
As operações load carregam um valor da memória para um registrador, o conteúdo que deve estar na memória apontada pelo argumento tem que estar de acordo com o tipo da instrução identificado pelo sufixo.
Operações store leem um ou mais dados do registrador e escrevem os mesmos no endereço passado como primeiro argumento.
Já a operação extract obtém um valor de uma parte do registrador identificado pelo valor imediato passado como segundo argumento. Esse valor é o índice do campo do registrador contando da direita para a esquerda começando em zero.
Exemplos:
Operações set
As instruções intrínsecas de set definem o valor de todos os campos do registrador ao mesmo tempo sem a necessidade de usar uma array para isso. Elas não são traduzidas para uma mas sim várias instruções em sequência, portanto pode haver uma penalidade de desempenho.
Tecnologia | Protótipo |
SSE2 |
|
SSE2 |
|
SSE2 |
|
SSE2 |
|
SSE2 |
|
SSE2 |
|
SSE |
|
SSE2 |
|
SSE |
|
As operações set escalares (_mm_set_sd
e_mm_set_ss
) definem o valor da parte menos significativa do registrador e zeram os demais valores.
As duas operações abaixo definem todos os campos do registrador para o mesmo valor passado como argumento:
Tecnologia | Protótipo |
SSE2 |
|
SSE |
|
Exemplo:
Operações matemáticas
Tecnologia | Protótipo | Instrução |
SSE2 |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE |
|
|
SSSE3 |
|
|
SSSE3 |
|
|
SSSE3 |
|
|
SSSE3 |
|
|
SSSE3 |
|
|
SSSE3 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE2 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE |
|
|
SSE |
|
|
SSE |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE4.1 |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE |
|
|
SSE |
|
|
SSE |
|
|
SSE2 |
|
|
SSE |
|
|
SSE2 |
|
|
SSE2 |
|
|
SSE |
|
|
SSE |
|
|
Exemplos:
Operações de randomização
As instruções intrínsecas abaixo leem um valor aleatório gerado por hardware:
Tecnologia | Protótipo | Instrução |
RDRAND |
|
|
RDRAND |
|
|
RDRAND |
|
|
A instrução rdrand
escreve o valor aleatório obtido no ponteiro passado como argumento. Ela deve ser usada em um loop pois não há garantia de que ela irá obter de fato um valor. Se obter o valor a função retorna 1
, caso contrário retorna 0
.
Exemplo:
Você deve compilar passando a flag -mrdrnd
para o GCC para indicar que o processador suporta a tecnologia. Caso contrário você obterá um erro como este:
error: inlining failed in call to always_inline ‘_rdrand32_step’: target specific option mismatch
As instruções intrínsecas abaixo são utilizadas da mesma maneira que rdrand porém o valor aleatório não é gerado por hardware.
Tecnologia | Protótipo | Instrução |
RDSEED |
|
|
RDSEED |
|
|
RDSEED |
|
|
É necessário compilar com a flag -mrdseed
para poder usar essas instruções intrínsecas.
Last updated