Open Source Your Knowledge, Become a Contributor
Technology knowledge has to be shared and made accessible for free. Join the movement.
Máscaras e Carregamento Condicional
Máscaras em Vetores
Na lição anterior, foi apresentado o conceito de máscara. Como é um conceito chave para controlar o fluxo de dados, é necessária uma explicação detalhada.
Uma máscara é um resultado de uma operação lógica entre vetores. Possui muitas similaridades com booleanos (eles são o resultado de operações lógicas em números únicos, ou em outros valores booleanos), mas, internamente, cada máscara deve ser composta somente por bits 1 ou por bits 0.
Vamos comparar dois vetores float AVX com o operador maior que:
As entradas são dois vetores com valores float. A saída dessa operação lógica, também é um vetor de valores float, mas os valores devem ter somente bits 0's ou somente bits 1's. Todos os 1's representam o valor lógico TRUE
, enquanto os 0's são o valor lógico FALSE
. O valor 1's é impresso como -nan
para floats, ou como -1 para inteiros. O valor real armazenado não é importante, somente é necessário saber que possui valores verdadeiros (TRUE
) ou valores falsos (FALSE
).
Resultado dos operadores lógicos (>, <, ==, &&, ||, etc)
Utilizando o operador && como um exemplo:
vector && vector
=mask
mask && mask
=mask
vector && mask
=?????
Eu ainda não testei o último caso, eu acho que retornará resultados inesperados. É como realizar 3 > false
, talvez em C++ funcione, mas no aspecto lógico, é incorreto.
NOTA: Diferentemente dos valores booleanos, em que qualquer valor diferente de zero é
TRUE
. Somente um vetor composto com todos os bits 1's é consideradoTRUE
. Não utilize outros valores como máscara, pois falhará ou retornará resultados inesperados.
Carregamento Condicional
Máscaras podem ser utilizadas para carregar condicionalmente valores em vetores. Se você relembrar as funções blend-based. Todas elas utilizam máscaras para controlar condicionalmente o carregamento de valores nos vetores:
if_select
(mask,value_true,value_false)
pode ser representado como:
Quando a máscara é definida como FALSE
, o dado é carregado do vetor value_false
, e quando é TRUE
, o dado vem do vetor value_true
. O conceito é simples, mas efetivo.
No próximo exercício, você precisa carregar um vetor de acordo com as seguintes condições:
if (value > 3.0f || (value <= -3.7f && value > -15.0f)) {
return sqrt(2.0f * value + 1.5f);
}
else {
return (-2.0f * value - 8.7f);
}
NOTA:
if_select
NÃO É um nome de função intrínseca. É o meu wrapper para_mm256_blendv_ps
. Por favor, note que_mm256_blendv_ps
possui uma ordem de parâmetros bem diferente! blendv tem a máscara como o último parâmetro!
Desempenho
Carregamento condicional utilizando máscaras não são uma branch real, então não possuem previsões errôneas, dessa forma a CPU pode fazer melhor uso da execução fora de ordem. Mas isso vem com um preço. Como vem sem uma branch, e toda a execução condicional é feita com operação em máscaras, ambas as branches são sempre calculadas e executadas.
Se você tiver um cálculo complexo para o value_false
, ele será sempre calculado, mesmo que ele aconteça em 0,00001% das vezes. Isso acarreta em problemas de desempenho se tiver partes do código que são realmente necessárias, mas computacionalmente caras.
Na próxima lição, nós iremos aprender algumas formas de controlar o fluxo de dados, sendo capazes de sair de laçoes baseado em algumas condições.