问题
AVX allow for bitwise logical operations such as and/or on floating point data-type __m256 and __m256d.
However, C++ doesn't allow for bitwise operations on floats and doubles, reasonably. If I'm right, there's no guarantee on the internal representation of floats, whether the compiler will use IEEE754 or not, hence a programmer can't be sure about how the bits of a float will look like.
Consider this example:
#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;
}
Assuming that IEEE754 is used, as the representation of -1 is 0xffffffff, I would expect the output to be
1,0,0,4,0,6,0,0
while it's instead
1 0 0 1.17549e-38 0 1.17549e-38 0 0
Hence my assumption about the internal representation was probably wrong (or I made some silly mistake).
So the question is: is there a way in which I can use floating point logical and be safe about the fact that the result will make sense?
回答1:
If you're using AVX intrinsics, then you know you're using IEEE754 floats, because that's what AVX does.
Some of the bitwise operations on floats that make sense are
- selecting, as in Jens' answer, though as of SSE4.1 we have
blendvps
and its relatives to do that in one instruction - absolute value (mask away the sign)
- negate (xor with -0.0f)
- transfer sign
- extracting the exponent (rare)
Mostly it's for manipulating the sign, or to selectively zero out whole floats, not so much for mucking about with individual bits of the exponent or significand - you can do it, but it's rarely useful.
回答2:
The reason is that there may be penalities for switching between domains of execution units bypass-delays-when-switching-execution-unit-domains and why-do-some-sse-mov-instructions-specify-that-they-move-floating-point-values. In this case switching from a floating point AVX execution unit to an integer execution AVX unit.
For example let's say you want to compare to floating point AVX registers x
and y
z = _mm256_cmp_ps(x, y, 1);
The AVX register z
contains boolean integer values (0 or -1) which you can then logical AND using _mm256_and_ps
or with _mm256_and_si256
if you wanted. But _mm256_and_ps
stays in the same execution unit and _mm256_and_si256
switches units which may cause a bypass delay.
Edit: in regards to bitwise operators on floats in C++ it certainly is possible and is sometimes useful. Here are some simple examples.
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
回答3:
The programmer can be perfectly sure of how single precision floating point are represented. How functions are implemented is another story. I have made use of the bitwise operations for implementing half-precision floats conforming to IEEE-754. I have also made use of the operations for branch-removal back in 2003 - before IBM filed a patent for this.
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 );
}
This example demonstrates how to remove a floating point branch using SSE2. The same can be achieved using AVX. If you try (the same technique) to remove branches using scalars you will not gain any performance due to the switch of context (applies to x86 - doesn't apply to ARM, where you have the fpsel operation)
来源:https://stackoverflow.com/questions/24943521/which-is-the-reason-for-avx-floating-point-bitwise-logical-operations