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=maskmask && mask=maskvector && 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_selectNÃO É um nome de função intrínseca. É o meu wrapper para_mm256_blendv_ps. Por favor, note que_mm256_blendv_pspossui 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.