整数数组相等性比较的向量化实现问题
在Windows平台使用clang++ 15.0.6编译器,添加编译标志-O3 -march=native --std=c++20时,编译器拒绝对以下等长未知长度整数数组的比较代码进行向量化:
bool compare(int const* lhs, int const* const lhs_end, int const* rhs) { while(lhs != lhs_end) { if(*lhs != *rhs) { return false; } lhs += 1; rhs += 1; } return true; }
手动展开循环后,编译器仍未生成向量化汇编,性能与原代码相近:
bool compare(int const* lhs, int const* const lhs_end, int const* rhs) { while(lhs_end - lhs >= 4) { bool const cmp1 = lhs[0] != rhs[0]; bool const cmp2 = lhs[1] != rhs[1]; bool const cmp3 = lhs[2] != rhs[2]; bool const cmp4 = lhs[3] != rhs[3]; if(cmp1 || cmp2 || cmp3 || cmp4) { return false; } lhs += 4; rhs += 4; } while(lhs != lhs_end) { if(*lhs != *rhs) { return false; } lhs += 1; rhs += 1; } return true; }
希望通过手动向量化提升比较性能,但找不到可对向量寄存器内容执行按位与/或操作的向量指令,请问是否有办法对该比较进行向量化或整体加速?
1. 直接使用标准库memcmp函数
这是最简便且高效的方案:由于两个数组是连续内存块且长度相等,直接调用memcmp即可。标准库的memcmp实现通常已做极致SIMD优化(如SSE/AVX),性能远超手动编写的朴素循环,甚至可能比手动向量代码更优。
示例代码:
bool compare(int const* lhs, int const* const lhs_end, int const* rhs) { size_t len = lhs_end - lhs; return memcmp(lhs, rhs, len * sizeof(int)) == 0; }
2. 调整循环结构,引导编译器自动向量化
编译器拒绝对原代码向量化的核心原因之一是指针到lhs_end的动态比较,换成计数式循环可消除该障碍,让clang更容易识别可向量化的模式:
bool compare(int const* lhs, int const* const lhs_end, int const* rhs) { size_t n = lhs_end - lhs; for (size_t i = 0; i < n; ++i) { if (lhs[i] != rhs[i]) { return false; } } return true; }
使用指定编译标志,clang会自动对这个计数循环进行向量化,生成SSE/AVX指令,大幅提升性能。
3. 手动使用编译器内置SIMD指令
如果一定要手动向量化,可利用clang支持的x86 SIMD内置函数(如SSE/AVX),核心思路是批量加载、批量比较、快速判断结果:
以SSE为例(一次处理4个int):
#include <immintrin.h> bool compare(int const* lhs, int const* const lhs_end, int const* rhs) { // 处理能被4整除的大块数据 while (lhs_end - lhs >= 4) { __m128i vec_lhs = _mm_loadu_si128(reinterpret_cast<const __m128i*>(lhs)); __m128i vec_rhs = _mm_loadu_si128(reinterpret_cast<const __m128i*>(rhs)); // 批量比较,相等元素对应位置置为0xFFFFFFFF,不等为0 __m128i cmp_result = _mm_cmpeq_epi32(vec_lhs, vec_rhs); // 检查是否所有元素都相等 if (_mm_test_all_ones(cmp_result) == 0) { return false; } lhs += 4; rhs += 4; } // 处理剩余1-3个元素 while (lhs != lhs_end) { if (*lhs != *rhs) { return false; } lhs++; rhs++; } return true; }
若CPU支持AVX,可改用__m256i及对应函数(_mm256_loadu_si256、_mm256_cmpeq_epi32、_mm256_testz_si256),一次处理8个int,进一步提升性能。
这里无需手动执行按位与/或,_mm_cmpeq_epi32已生成比较掩码,_mm_test_all_ones可直接判断是否所有元素相等。
内容的提问来源于stack exchange,提问作者An0num0us

