gpt4 book ai didi

c - 使用 AVX2 和 SSE2 进行位 vector 运算

转载 作者:行者123 更新时间:2023-12-02 04:03:04 26 4
gpt4 key购买 nike

我是 AVX2 和 SSE2 指令集的新手,我想了解有关如何使用此类指令集来加速位 vector 运算的更多信息。

到目前为止,我已经成功地使用它们通过 double /浮点运算对代码进行矢量化。

在此示例中,我有一个 C++ 代码,用于在将位 vector (使用无符号整数)中的某个位设置为特定值之前检查条件:

int process_bit_vetcor(unsigned int *bitVector, float *value, const float threshold, const unsigned int dim)
{
int sum = 0, cond = 0;

for (unsigned int i = 0; i < dim; i++) {
unsigned int *word = bitVector + i / 32;
unsigned int bitValue = ((unsigned int)0x80000000 >> (i & 0x1f));
cond = (value[i] <= threshold);
(*word) = (cond) ? (*word) | bitValue : (*word);
sum += cond;
}

return sum;
}

变量sum仅返回条件为TRUE的情况数。

我尝试用 SSE2 和 AVX2 重写这个例程,但没有成功...:-(

是否可以使用 AVX2 和 SSE2 重写此类 C++ 代码?对于这种类型的位运算是否值得使用矢量化?位 vector 可能包含数千个位,因此我希望使用 SSE2 和 AVX2 来加速会很有趣。

提前致谢!

最佳答案

如果dim,则以下内容应该有效是 8 的倍数(要处理余数,请在末尾添加一个简单的循环)。较小的 API 更改:

  • 使用long而不是unsigned int for 循环索引(这有助于 clang 展开循环)
  • 假设bitvector是小尾数(如评论中建议的)

在循环内,bitVector按字节访问。将 movemask 的 2 或 4 个结果组合起来可能是值得的。并立即对它们进行位或(可能取决于目标架构)。

计算sum ,直接根据 cmp_ps 的结果计算出 8 个部分和。手术。由于无论如何您都需要位掩码,因此可能值得使用 popcnt (理想情况下将 2、4 或 8 个字节组合在一起之后——同样,这可能取决于您的目标架构)。

int process_bit_vector(uint32_t *bitVector32, float *value,
const float threshold_float, const long dim) {
__m256i sum = _mm256_setzero_si256();
__m256 threshold_vector = _mm256_set1_ps(threshold_float);
uint8_t *bitVector8 = (uint8_t *)bitVector32;

for (long i = 0; i <= dim-8; i += 8) {
// compare next 8 values with threshold
// (use threshold as first operand to allow loading other operand from memory)
__m256 cmp_mask = _mm256_cmp_ps(threshold_vector, _mm256_loadu_ps(value + i), _CMP_GE_OQ);
// true values are `-1` when interpreted as integers, subtract those from `sum`
sum = _mm256_sub_epi32(sum, _mm256_castps_si256(cmp_mask));
// extract bitmask
int mask = _mm256_movemask_ps(cmp_mask);
// bitwise-or current mask with result bit-vector
*bitVector8++ |= mask;
}

// reduce 8 partial sums to a single sum and return
__m128i sum_reduced = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum,1));
sum_reduced = _mm_add_epi32(sum_reduced, _mm_srli_si128(sum_reduced, 8));
sum_reduced = _mm_add_epi32(sum_reduced, _mm_srli_si128(sum_reduced, 4));

return _mm_cvtsi128_si32(sum_reduced);
}

Godbolt 链接:https://godbolt.org/z/ABwDPe

  • 由于某种原因,GCC 这样做 vpsubd ymm2, ymm0, ymm1; vmovdqa ymm0, ymm2;而不仅仅是vpsubd ymm0, ymm0, ymm1 .
  • Clang 未能加入 loadvcmpps (并使用 LE 而不是 GE 比较)——如果您不关心 NaN 的处理方式,则可以使用 _CMP_NLT_US而不是_CMP_GE_OQ .
<小时/>

具有大端输出的修订版本(未经测试):

int process_bit_vector(uint32_t *bitVector32, float *value,
const float threshold_float, const long dim) {
int sum = 0;
__m256 threshold_vector = _mm256_set1_ps(threshold_float);

for (long i = 0; i <= dim-32; i += 32) {
// compare next 4x8 values with threshold
// (use threshold as first operand to allow loading other operand from memory)
__m256i cmp_maskA = _mm256_castps_si256(_mm256_cmp_ps(threshold_vector, _mm256_loadu_ps(value + i+ 0), _CMP_GE_OQ));
__m256i cmp_maskB = _mm256_castps_si256(_mm256_cmp_ps(threshold_vector, _mm256_loadu_ps(value + i+ 8), _CMP_GE_OQ));
__m256i cmp_maskC = _mm256_castps_si256(_mm256_cmp_ps(threshold_vector, _mm256_loadu_ps(value + i+16), _CMP_GE_OQ));
__m256i cmp_maskD = _mm256_castps_si256(_mm256_cmp_ps(threshold_vector, _mm256_loadu_ps(value + i+24), _CMP_GE_OQ));

__m256i cmp_mask = _mm256_packs_epi16(
_mm256_packs_epi16(cmp_maskA,cmp_maskB), // b7b7b6b6'b5b5b4b4'a7a7a6a6'a5a5a4a4 b3b3b2b2'b1b1b0b0'a3a3a2a2'a1a1a0a0
_mm256_packs_epi16(cmp_maskC,cmp_maskD) // d7d7d6d6'd5d5d4d4'c7c7c6c6'c5c5c4c4 d3d3d2d2'd1d1d0d0'c3c3c2c2'c1c1c0c0
); // cmp_mask = d7d6d5d4'c7c6c5c4'b7b6b5b4'a7a6a5a4 d3d2d1d0'c3c2c1c0'b3b2b1b0'a3a2a1a0

cmp_mask = _mm256_permute4x64_epi64(cmp_mask, 0x8d);
// cmp_mask = [b7b6b5b4'a7a6a5a4 b3b2b1b0'a3a2a1a0 d7d6d5d4'c7c6c5c4 d3d2d1d0'c3c2c1c0]
__m256i shuff_idx = _mm256_broadcastsi128_si256(_mm_set_epi64x(0x00010203'08090a0b,0x04050607'0c0d0e0f));
cmp_mask = _mm256_shuffle_epi8(cmp_mask, shuff_idx);

// extract bitmask
uint32_t mask = _mm256_movemask_epi8(cmp_mask);
sum += _mm_popcnt_u32 (mask);
// bitwise-or current mask with result bit-vector
*bitVector32++ |= mask;
}

return sum;
}

这个想法是在应用 vpmovmskb 之前对字节进行打乱。在上面。这需要对 32 个输入值进行 5 次洗牌操作(包括 3 个 vpacksswb ),但总和的计算是使用 popcnt 完成的。而不是 4 vpsubdvpermq ( _mm256_permute4x64_epi64 ) 可以通过在比较之前策略性地将 128 位一半加载到 256 位 vector 中来避免。另一个想法(因为无论如何你都需要对最终结果进行洗牌)是将部分结果混合在一起(这往往需要 p52*p015 在我检查的架构上,所以可能不值得)。

关于c - 使用 AVX2 和 SSE2 进行位 vector 运算,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58693907/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com