如何进一步优化基于数组操作的XOR运算代码?
数组XOR运算性能优化求助
我正在对result和DB两个数组执行XOR运算,result通过偏移量rotate1进行访问。目前已采用AVX2指令集、循环展开和预取优化,但仍存在性能瓶颈:50%时间消耗在XOR操作,40%在数据存储,分支的else部分每次调用仅执行一次。现提供代码及详细实现细节,寻求进一步优化方案:
void perform_array_xor(uint32_t partindex, uint32_t offset, uint64_t *result, uint32_t EntrySize, uint32_t PartSize) { auto B = 1; assert(EntrySize/8==B); // Ensure that PartSize is a multiple of 32 for this example if (PartSize % 8 != 0) { // Handle this case return; } __m256i a,b,r; unsigned int rotate1_1; int k; for (int i = 0; i < PartSize; i += 8) { rotate1_1 = (i + offset) & (PartSize - 1); _mm_prefetch(result + rotate1_1, _MM_HINT_T2); k = 0; if(rotate1_1 + 7 < PartSize){ a = _mm256_loadu_si256((__m256i*)(result + rotate1_1)); b = _mm256_loadu_si256((__m256i*)(DB + partindex + i)); r = _mm256_xor_si256(a, b); _mm256_storeu_si256((__m256i*)(result + rotate1_1), r); //std::memcpy(result + rotate1_1, &r, sizeof(__m256i)); k = 4 ; a = _mm256_loadu_si256((__m256i*)(result + rotate1_1 + k)); b = _mm256_loadu_si256((__m256i*)(DB + partindex + i + k)); r = _mm256_xor_si256(a, b); _mm256_storeu_si256((__m256i*)(result + rotate1_1 + k), r); //std::memcpy(result + rotate1_1 + k, &r, sizeof(__m256i)); } else{ result[(rotate1_1 + 0) & (PartSize - 1)] ^= DB[partindex + (i + 0)]; result[(rotate1_1 + 1) & (PartSize - 1)] ^= DB[partindex + (i + 1)]; result[(rotate1_1 + 2) & (PartSize - 1)] ^= DB[partindex + (i + 2)]; result[(rotate1_1 + 3) & (PartSize - 1)] ^= DB[partindex + (i + 3)]; result[(rotate1_1 + 4) & (PartSize - 1)] ^= DB[partindex + (i + 4)]; result[(rotate1_1 + 5) & (PartSize - 1)] ^= DB[partindex + (i + 5)]; result[(rotate1_1 + 6) & (PartSize - 1)] ^= DB[partindex + (i + 6)]; result[(rotate1_1 + 7) & (PartSize - 1)] ^= DB[partindex + (i + 7)]; } } }
更新信息
- DB数组大小为2^28,总计2GB数据
- result数组大小为2^14,总计128KB
- 每次函数调用时result数组保持不变
- 每次调用会访问DB中连续的2^14个条目
- 当前处理完整DB耗时143ms,约13GB/s
- 目标平台为9代i7处理器,编译器为Clang
优化方案
1. 消除分支预测开销
既然else分支仅执行一次,直接把循环拆分为两部分:先处理所有不需要进入else的完整迭代,最后单独处理触发else的那一轮,彻底移除循环内的分支判断,避免分支预测失败打断CPU流水线。
2. 优化内存访问模式
result仅128KB,完全能被L1缓存容纳,当前的手动预取属于冗余操作,建议移除_mm_prefetch调用,让CPU硬件预取器自动处理。- 对
DB的连续访问可以添加显式预取,比如在循环中预取DB + partindex + i + 32(提前1-2个缓存行),进一步掩盖内存读取延迟。
3. AVX2指令对齐优化
- 将
_mm256_loadu_si256/_mm256_storeu_si256替换为对齐版本_mm256_load_si256/_mm256_store_si256,前提是确保result和DB按32字节对齐。可通过编译器属性__attribute__((aligned(32)))或内存分配函数_mm_malloc实现,对齐访问能大幅提升内存读写效率。 - 进一步展开循环,一次处理16个元素(四个256位向量),提升指令级并行度,让CPU执行单元充分利用。
4. 指令调度优化
调整指令顺序,将加载、XOR、存储操作交错排列,利用CPU乱序执行特性重叠处理不同操作,隐藏存储和计算的延迟:
// 交错执行两组向量操作 a1 = _mm256_load_si256(...); b1 = _mm256_load_si256(...); a2 = _mm256_load_si256(...); b2 = _mm256_load_si256(...); r1 = _mm256_xor_si256(a1, b1); r2 = _mm256_xor_si256(a2, b2); _mm256_store_si256(...); _mm256_store_si256(...);
5. 强化编译器优化选项
确保开启以下编译选项:
-O3 -mavx2 -march=skylake -funroll-loops
显式指定Skylake架构(9代i7属于该架构),让编译器生成更针对性的优化指令;-funroll-loops辅助循环展开,提升并行度。
6. 重构循环逻辑
提前计算循环次数,将DB指针缓存到局部变量,减少内存寻址的间接开销;利用PartSize是2的幂的特性,缓存PartSize -1为局部变量,减少重复计算。
优化后示例代码
void perform_array_xor(uint32_t partindex, uint32_t offset, uint64_t *result, uint32_t EntrySize, uint32_t PartSize) { const auto B = 1; assert(EntrySize / 8 == B); assert(PartSize % 8 == 0); // 提前断言,移除运行时判断 __m256i a1, b1, r1, a2, b2, r2; const uint64_t* db_ptr = DB + partindex; const uint32_t part_size_mask = PartSize - 1; const uint32_t full_loop_count = (PartSize / 8) - 1; // 处理所有完整迭代,无分支 for (int i = 0; i < full_loop_count; ++i) { const uint32_t rotate1_1 = (offset + i * 8) & part_size_mask; // 交错加载,提升指令并行度 a1 = _mm256_load_si256((__m256i*)(result + rotate1_1)); b1 = _mm256_load_si256((__m256i*)(db_ptr + i * 8)); a2 = _mm256_load_si256((__m256i*)(result + rotate1_1 + 4)); b2 = _mm256_load_si256((__m256i*)(db_ptr + i * 8 + 4)); // XOR运算 r1 = _mm256_xor_si256(a1, b1); r2 = _mm256_xor_si256(a2, b2); // 存储结果 _mm256_store_si256((__m256i*)(result + rotate1_1), r1); _mm256_store_si256((__m256i*)(result + rotate1_1 + 4), r2); } // 单独处理最后一轮,避免循环内分支 const int last_i = full_loop_count * 8; const uint32_t rotate1_1 = (offset + last_i) & part_size_mask; if (rotate1_1 + 7 < PartSize) { a1 = _mm256_load_si256((__m256i*)(result + rotate1_1)); b1 = _mm256_load_si256((__m256i*)(db_ptr + last_i)); r1 = _mm256_xor_si256(a1, b1); _mm256_store_si256((__m256i*)(result + rotate1_1), r1); a2 = _mm256_load_si256((__m256i*)(result + rotate1_1 + 4)); b2 = _mm256_load_si256((__m256i*)(db_ptr + last_i + 4)); r2 = _mm256_xor_si256(a2, b2); _mm256_store_si256((__m256i*)(result + rotate1_1 + 4), r2); } else { result[(rotate1_1 + 0) & part_size_mask] ^= db_ptr[last_i + 0]; result[(rotate1_1 + 1) & part_size_mask] ^= db_ptr[last_i + 1]; result[(rotate1_1 + 2) & part_size_mask] ^= db_ptr[last_i + 2]; result[(rotate1_1 + 3) & part_size_mask] ^= db_ptr[last_i + 3]; result[(rotate1_1 + 4) & part_size_mask] ^= db_ptr[last_i + 4]; result[(rotate1_1 + 5) & part_size_mask] ^= db_ptr[last_i + 5]; result[(rotate1_1 + 6) & part_size_mask] ^= db_ptr[last_i + 6]; result[(rotate1_1 + 7) & part_size_mask] ^= db_ptr[last_i + 7]; } }
内容的提问来源于stack exchange,提问作者CryptoKitty
相关产品推荐
相关产品推荐

