字节数组置换SSE优化
byte array permute SSE optimization
我想使用SSE内部函数来翻译这段代码。我发现pshufb
SSSE3指令和类似的__builtin_ia32_pshufb128(v128i, v128i)
GCC内在可能与此代码一起使用。该代码通过以特定方式交换数组中的字节来通过索引k
来置换字节s
的向量。
void permutation(int k, std::vector<char> & s)
{
for(size_t j = 1; j < s.size(); ++j)
{
std::swap(s[k % (j + 1)], s[j]);
k = k / (j + 1);
}
}
我花了一个小时思考如何将代码翻译成pshufb
。是否可以用单个pshufb
排列16个字节,或者需要多个指令?足够好的解决方案一次只排列16个字节。
编辑:问题的进一步背景:我正在迭代s
的所有可能的排列。对于相同的s
,提前计算k = 0, 1, 2,...
的多个结果是可以的。但是,我需要稍后最好将第10个CCD_排列再现为O(1)运算。
单次调用
请注意,您可以在混合基数的位置记法系统中记下数字k
,以便此表示中的每个数字都将为j
的几个连续值定义交换元素的索引。
例如,对于长度为12的字符串,您可以将任何k
写为带基数的三位数:
720 = 1*2*3*4*5*6 (0-th digit, lowest value)
504 = 7*8*9 (1-th digit)
1320 = 10*11*12 (2-th digit, highest value)
现在,您可以为每个位置和该位置中数字的每个值预先计算所有元素的累积排列,并将其保存在查找表中。然后,您就可以通过一条指令进行多次交换。
以下是一个示例(预计算将是最困难的部分):
//to be precomputed:
__m128i mask0[ 720];
__m128i mask1[ 504];
__m128i mask2[1320];
__m128i permutation(int k, __m128i s) {
s = _mm_shuffle_epi8(s, mask0[k % 720]); k /= 720; //j = [1..5]
s = _mm_shuffle_epi8(s, mask1[k % 504]); k /= 504; //j = [6..8]
s = _mm_shuffle_epi8(s, mask2[k ]); //j = [9..11]
return s;
}
为了在步骤数量和查找表大小之间取得平衡,可以将分解更改为基。
注意:除法运算非常缓慢。只使用编译时间常数的除法,然后优化器会将它们转换为乘法。检查生成的程序集,以确保没有分割说明。
很多电话
不幸的是,索引计算仍然会占用建议解决方案的大部分时间,请参阅生成的程序集。如果同时处理几个连续的k
值,则可以显著减少此开销。
优化解决方案的最简单方法是:分别对k
的数字进行迭代,而不是对k
进行单个循环。然后指数计算就变得不必要了。此外,还可以重用部分计算的结果。
__m128i s;// = ???
for (int k0 = 0; k0 < 720; k0++) {
__m128i s0 = _mm_shuffle_epi8(s, mask0[k0]);
for (int k1 = 0; k1 < 504; k1++) {
__m128i s1 = _mm_shuffle_epi8(s0, mask1[k1]);
for (int k2 = 0; k2 < 1320; k2+=4) {
//for k = (((k2+0) * BASE1) + k1) * BASE0 + k0:
__m128i sx0 = _mm_shuffle_epi8(s1, mask2[k2+0]);
//for k = (((k2+1) * BASE1) + k1) * BASE0 + k0:
__m128i sx1 = _mm_shuffle_epi8(s1, mask2[k2+1]);
//for k = (((k2+2) * BASE1) + k1) * BASE0 + k0:
__m128i sx2 = _mm_shuffle_epi8(s1, mask2[k2+2]);
//for k = (((k2+3) * BASE1) + k1) * BASE0 + k0:
__m128i sx3 = _mm_shuffle_epi8(s1, mask2[k2+3]);
// ... check four strings: sx0, sx1, sx2, sx3
}
}
}
这样,平均每个排列需要进行一次洗牌(请参阅汇编),这似乎近乎完美。
代码和结果
以下是所有解决方案的完整工作代码。
请注意,查找表的生成并不是一件简单的事情,而且相应的代码相当大(并且充满了令人讨厌的细节)。
在Intel Core 2 Duo E4700 Allendale(2600MHz)上运行的基准测试给出了以下结果:
2.605 s: original code (k < 12739451)
0.125 s: single-call fast code (k < 12739451)
4.822 s: single-call fast code (k < 479001600)
0.749 s: many-call fast code (k < 479001600)
因此,单次调用版本大约比原始代码快20倍,多次调用版本比单次调用版快6.5多倍。
- 空基优化子对象的地址
- 关闭||运算符优化
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 如何用SSE优化矩阵3乘3乘法与点?
- SSE 内联汇编和可能的 g++ 优化错误
- SSE优化平方差的总和
- 用于在数组中找到零并切换标志 更新另一个数组的循环的SSE优化
- 字节数组置换SSE优化
- SSE本质-逻辑非优化
- 当函数参数是常量引用临时或按值复制的临时时,为什么 MSVC 优化会破坏 SSE 代码
- 高斯模糊的SSE优化
- 如何使用 SSE 或 GLSL 优化"u[0]*v[0] + u[2]*v[2]"代码行
- 灰度双线性补丁提取-SSE优化
- C++SLMATH库和SSE优化
- 具有多种功能的可视化C++SSE优化
- 确定eigen是否为SSE指令优化了代码
- SSE微优化指令顺序
- 您将如何编写可能优化为一条SSE指令的无符号加法代码
- 在同一个可执行文件中使用C/ c++进行不同的优化(plain、SSE、AVX)
- SSE指令在实践中优化了什么,编译器如何启用和使用它们