C++ 使用SIMD指令的并行二项式系数
背景 我最近一直在使用一些旧代码(~1998年)并重新编写一些代码以提高性能。以前在状态的基本数据结构中,我将元素存储在几个数组中,现在我使用原始位(对于需要少于64位的情况)。也就是说,以前我有一个C++ 使用SIMD指令的并行二项式系数,c++,simd,intrinsics,avx,binomial-coefficients,C++,Simd,Intrinsics,Avx,Binomial Coefficients,背景 我最近一直在使用一些旧代码(~1998年)并重新编写一些代码以提高性能。以前在状态的基本数据结构中,我将元素存储在几个数组中,现在我使用原始位(对于需要少于64位的情况)。也就是说,以前我有一个b元素数组,现在我在一个64位整数中设置了b位,以指示该值是否是我状态的一部分 使用诸如\u pext\u u64和\u pdep\u u64之类的内部函数,我成功地将所有操作的速度提高了5-10倍。我正在做最后一个操作,它与计算一个完美的散列函数有关 哈希函数的确切细节并不太重要,但它可以归结为计
b
元素数组,现在我在一个64位整数中设置了b
位,以指示该值是否是我状态的一部分
使用诸如\u pext\u u64
和\u pdep\u u64
之类的内部函数,我成功地将所有操作的速度提高了5-10倍。我正在做最后一个操作,它与计算一个完美的散列函数有关
哈希函数的确切细节并不太重要,但它可以归结为计算二项式系数(n选择k
-n!/((n-k)!k!)
用于各种n
和k
。我当前的代码为此使用了一个大的查找表,它本身可能很难显著加快速度(表中可能存在的缓存未命中除外,我尚未测量)
但是,我在想,使用SIMD指令,我可能能够直接并行计算多个状态的这些值,从而看到整体性能的提升
一些制约因素:
- 在每个64位状态(表示小数字)中,始终精确地设置了
位b
- 二项式系数中的
值与k
相关,并且在计算中变化均匀。这些值很小(大多数情况下这里有一种可能的解决方案,一次使用一种状态从查找表进行计算。在多个状态上并行进行计算可能比使用单一状态更有效。注意:这是硬编码的,用于获得6个元素组合的固定情况b
int64_t GetPerfectHash2(State &s) { // 6 values will be used __m256i offsetsm1 = _mm256_setr_epi32(6*boardSize-1,5*boardSize-1, 4*boardSize-1,3*boardSize-1, 2*boardSize-1,1*boardSize-1,0,0); __m256i offsetsm2 = _mm256_setr_epi32(6*boardSize-2,5*boardSize-2, 4*boardSize-2,3*boardSize-2, 2*boardSize-2,1*boardSize-2,0,0); int32_t index[9]; uint64_t value = _pext_u64(s.index2, ~s.index1); index[0] = boardSize-numItemsSet+1; for (int x = 1; x < 7; x++) { index[x] = boardSize-numItemsSet-_tzcnt_u64(value); value = _blsr_u64(value); } index[8] = index[7] = 0; // Load values and get index in table __m256i firstLookup = _mm256_add_epi32(_mm256_loadu_si256((const __m256i*)&index[0]), offsetsm2); __m256i secondLookup = _mm256_add_epi32(_mm256_loadu_si256((const __m256i*)&index[1]), offsetsm1); // Lookup in table __m256i values1 = _mm256_i32gather_epi32(combinations, firstLookup, 4); __m256i values2 = _mm256_i32gather_epi32(combinations, secondLookup, 4); // Subtract the terms __m256i finalValues = _mm256_sub_epi32(values1, values2); _mm256_storeu_si256((__m256i*)index, finalValues); // Extract out final sum int64_t result = 0; for (int x = 0; x < 6; x++) { result += index[x]; } return result; }
int64\t GetPerfectHash2(州和州) { //将使用6个值 __m256i偏移量M1=_mm256_setr_epi32(6*boardSize-1,5*boardSize-1, 4*boardSize-1,3*boardSize-1, 2*boardSize-1,1*boardSize-1,0,0); __m256i偏移量M2=_mm256_setr_epi32(6*boardSize-2,5*boardSize-2, 4*boardSize-2,3*boardSize-2, 2*boardSize-2,1*boardSize-2,0,0); int32_t指数[9]; uint64_t值=_pext_u64(s.index2,~s.index1); 索引[0]=boardSize NumitemSet+1; 对于(int x=1;x<7;x++) { 索引[x]=板尺寸NUMITEMSET-tzcnt_64(值); 值=_blsr_u64(值); } 指数[8]=指数[7]=0; //在表中加载值并获取索引 __m256i firstLookup=\u mm256\u add\u epi32(\u mm256\u loadu\u si256((常数m256i*)和索引[0]),偏移量m2); __m256i secondLookup=\u mm256\u add\u epi32(\u mm256\u loadu\u si256((const\u m256i*)和index[1]),偏移量为1; //在表中查找 __m256i值1=_mm256_i32gather_epi32(组合,第一次查找,4); __m256i值2=_mm256_i32gather_epi32(组合,二次查找,4); //减去术语 __m256i最终值=_mm256_sub_epi32(值1,值2); _mm256存储si256((uu m256i*)索引,最终值; //提取最终金额 int64_t结果=0; 对于(int x=0;x<6;x++) { 结果+=指数[x]; } 返回结果; }
请注意,我实际上有两种类似的情况。在第一种情况下,我不需要
,并且此代码比我现有的代码慢约3倍。在第二种情况下,我需要它,速度快25%。我们可以假设AVX2(以及收集负载的可用性)吗?使用不同的哈希函数是一种选项吗?x86上不提供SIMD整数除法,除非通过乘法逆(对常数除数有效)或浮点或双精度的转换。将位提取为适合SIMD指令的值。这是看待SIMD的错误方式。当您将64位整数加载到SIMD向量中时,它已经是8x 8位整数和4x 16位整数的向量,依此类推。您可以在\u pext\u u64
\u m128i变量。如果您需要更宽的中间精度,那么是的,第一步通常是类似于
或类似()如果pmovzxbd
总是很小,那么除数实际上是常量。或者你是说这些值是可变长度的比特组,你需要迭代解析,以找出一个结束点和下一个开始点?那么是的,你可能需要一个标量循环。我想至少有一些(伪)至少一个标量版本的代码会有帮助;我真的不想知道需要加速哪些操作。可能对16位或32位整数SIMD除以小常量会有帮助。(方法与)k