目标:使用最少数量的aritmetic oeprations(即每个{mask1 AND mask2})识别要转换为4 boolean“uint8_t”的内在函数。
UPDATE:为了优化代码,我在C ++中使用SIMD。与Loading 8 chars from memory into an __m256 variable as packed single precision floats相比,目标是处理/支持大规模阵列的掩码。后者使用'internal'mask-properties(“https://software.intel.com/sites/landingpage/IntrinsicsGuide/#expand=10,13”)来实现:
uint8_t mask1[4] = {0, 1, 1, 0}; uint8_t mask2[4] = {1, 1, 0, 0}; float data[4] = {5, 4, 2, 1};
{ //! Naive code which works:
float sum = 0;
for(int i = 0; i < 4; i++) {
if(mask1[i] && mask2[i]) {sum += data[i];}
}
}
从上面我们观察到使用掩码combiend和简单算术:虽然优化算法支持上述操作集,但'内部'有几个弱点':( a)约束操作次数和(b)放置更新编译器的要求(情况并非总是如此)。
背景: 挑战涉及从“char”数据类型到“float”数据类型的转换。为了演示我的代码中的错误,这里有一个简短的摘录:
//! Setup, a setup which is wrong as mask1 and mask2 are chars and not floats.
#include <emmintrin.h>
#include <x86intrin.h>
char mask1[4] = {0, 1, 0, 1};
char mask2[4] = {1, 0, 0, 1};
const int j = 0;
//! The logics, which is expected to work correct for flroats, ie, not chars.
const __m128 vec_empty_empty = _mm_set1_ps(0);
const __m128 vec_empty_ones = _mm_set1_ps(1);
const __m128 term1 = _mm_load_ps(&rmul1[j2]);
const __m128 term2 = mm_load_ps(&rmul2[j2]);
__m128 vec_cmp_1 = _mm_cmplt_ps(term1, vec_empty_empty);
__m128 vec_cmp_2 = _mm_cmplt_ps(term2, vec_empty_empty);
//! Intersect the values: included to allow other 'empty values' than '1'.
vec_cmp_1 = _mm_and_ps(vec_cmp_1, vec_empty_ones);
vec_cmp_2 = _mm_and_ps(vec_cmp_2, vec_empty_ones);
//! Seperately for each 'cell' find the '1's which are in both:
__m128 mask = _mm_and_ps(vec_cmp_1, vec_cmp_2);
上面的结果用于交叉(即,乘)浮点向量float arr[4]
。因此,如果有人对如何将SIMD char矢量转换为浮动SIMD矢量有任何建议,我会非常感激! ;)
答案 0 :(得分:2)
使用SSE4.1 pmovsxbd
或pmovzxbd
将4字节的块符号或零扩展为32位整数元素的16B向量。
请注意,using pmovzxbd
(_mm_epi8_epi32
) as a load似乎不可能安全有效地编写,因为内存不具有较窄的内存操作数。
要进行比较部分,请使用pcmpeqd
生成元素中全零或全一位的掩码(即-1
)。用它来掩盖FP数据的向量。 (全零是IEEE浮点数中0.0
的位表示,0.0是附加标识。)
如果你的元素总是只有0或1,你可以使用uint32_t
来保存所有四个字节并使用标量AND(C&#39; s &
运算符)作为SWAR实现所有四个mask1[i] && mask2[i]
检查。将该整数转换为向量pmovsxbd
。如果你的元素实际上是0和-1(全1),这将更好地工作,否则你需要额外的步骤来获得矢量蒙版。 (例如pcmpeqb对和全零向量)。
如果您不能使用-1
代替1
,那么您最好的选择可能仍然是将两个掩码解包为32位元素和pcmpeqd
。
一般的想法是:
__m128i m1vec = _mm_epi8_epi32(mask1); // where mask1 has to be a __m128i vector already, not a 4byte memory location.
__m128i m2vec = _mm_epi8_epi32(mask2);
// sign-extension turns each 0 or -1 byte into a 0 or -1 dword (32bit) element
__m128i mask = _mm_and_si128(mask1, mask2);
// convert from 0/1 to 0/-1 if necessary. I'm assuming the simple case.
__m128 masked_floats = _mm_and_ps(floats, _mm_castsi128_ps(mask)); // 0.0 or original value
sum = _mm_add_ps(sum, masked_floats);
如果掩码元素可以是0 / -1以外的其他值,则可能需要使用_mm_cmpeq_epi32(m1vec, _mm_setzero_si128())
或其他东西分别对它们进行boolean化。 (这会将非零变为零,反之亦然)
请参阅x86标记维基以获取链接,尤其是https://software.intel.com/sites/landingpage/IntrinsicsGuide/