AVX допускает побитовые логические операции, такие как и / или над типом данных с плавающей запятой __m256 и __m256d.
Тем не менее, C ++ не допускает побитовых операций над числами с плавающей точкой и удваивается, разумно. Если я прав, нет гарантии на внутреннее представление чисел с плавающей точкой, будет ли компилятор использовать IEEE754 или нет, поэтому программист не может быть уверен в том, как будут выглядеть биты с плавающей точкой.
Рассмотрим этот пример:
#include <immintrin.h>
#include <iostream>
#include <limits>
#include <cassert>
int main() {
float x[8] = {1,2,3,4,5,6,7,8};
float mask[8] = {-1,0,0,-1,0,-1,0,0};
float x_masked[8];
assert(std::numeric_limits<float>::is_iec559);
__m256 x_ = _mm256_load_ps(x);
__m256 mask_ = _mm256_load_ps(mask);
__m256 x_masked_ = _mm256_and_ps(x_,mask_);
_mm256_store_ps(x_masked,x_masked_);
for(int i = 0; i < 8; i++)
std::cout << x_masked[i] << " ";
return 0;
}
Предполагая, что используется IEEE754, поскольку представление -1 равно 0xffffffff, я ожидаю, что результат будет
1,0,0,4,0,6,0,0
пока это вместо
1 0 0 1.17549e-38 0 1.17549e-38 0 0
Следовательно, мое предположение о внутреннем представлении, вероятно, было неверным (или я допустил какую-то глупую ошибку).
Таким образом, вопрос заключается в следующем: есть ли способ, которым я могу использовать логические числа с плавающей запятой и быть уверенным в том факте, что результат будет иметь смысл?
Если вы используете встроенные функции AVX, то вы знаете, что используете плавающие объекты IEEE754, потому что это то, что делает AVX.
Некоторые из побитовых операций над числами с плавающей запятой, которые имеют смысл:
blendvps
и его родственники, чтобы сделать это в одной инструкцииВ основном это для манипулирования знаком или для выборочного обнуления целых чисел, а не для того, чтобы разбираться с отдельными битами показателя или значения — вы можете это сделать, но это редко полезно.
Причина в том, что могут быть штрафы за переключение между доменами исполнительных блоков двухконтурности задержка-когда-коммутационный-исполнение-блок-домены
а также почему-Do-некоторые-SSE-МЫ-инструкция, указать, что: они въезд с плавающей точкой стоимости. В этом случае переключение с исполнительного блока AVX с плавающей запятой на целочисленный исполнительный блок AVX.
Например, допустим, вы хотите сравнить регистры AVX с плавающей запятой x
а также y
z = _mm256_cmp_ps(x, y, 1);
Регистр AVX z
содержит логические целочисленные значения (0 или -1), которые затем можно логически И, используя _mm256_and_ps
или с _mm256_and_si256
если ты хотел. Но _mm256_and_ps
остается в том же исполнительном блоке и _mm256_and_si256
переключает блоки, которые могут вызвать задержку байпаса.
Редактировать: в отношении побитовых операторов на числах с плавающей точкой в C ++ это, конечно, возможно и иногда полезно. Вот несколько простых примеров.
union {
float f;
int i;
} u;
u.i ^= 0x80000000; // flip sign bit of u.f
u.i &= 0x7FFFFFFF; // set sign bit to zero //take absolute value
Программист может быть совершенно уверен, как представлены числа с плавающей запятой одинарной точности. Как реализованы функции — это другая история. Я использовал побитовые операции для реализации поплавков с половинной точностью, соответствующих IEEE-754. Я также использовал операции по удалению веток еще в 2003 году — до того, как IBM подала на это патент.
static inline __m128 _mm_sel_ps(__m128 a, __m128 b, __m128 mask ) {
b = _mm_and_ps( b, mask );
a = _mm_andnot_ps( mask, a );
return _mm_or_ps( a, b );
}
Этот пример демонстрирует, как удалить ветвь с плавающей запятой, используя SSE2. То же самое может быть достигнуто с помощью AVX. Если вы попытаетесь (той же техникой) удалить ветви с помощью скаляров, вы не получите никакой производительности из-за переключения контекста (относится к x86 — не относится к ARM, где у вас есть операция fpsel)