gpt4 book ai didi

c++ - 字节数组置换 SSE 优化

转载 作者:IT老高 更新时间:2023-10-28 23:21:28 25 4
gpt4 key购买 nike

我想使用 SSE 内在函数翻译此代码。我找到了可能与此代码一起使用的 pshufb SSSE3 指令和类似的 __builtin_ia32_pshufb128(v128i, v128i) GCC 内在函数。该代码通过以特定方式交换数组中的字节,通过索引 k 置换字节 vector 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 的所有可能排列。提前计算 k = 0, 1, 2,... 相同 s 的多个结果是可以的。但是我需要稍后重现 k-th 排列,最好是作为 O(1) 操作。

最佳答案

单次调用

请注意,您可以使用 mixed radix 在位置符号系统中写下数字 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;
}

您可以改变分解为基数,以便在步骤数和查找表大小之间取得平衡。

注意:除法运算很慢。仅使用编译时常量的除法,然后优化器会将它们转换为乘法。检查生成的程序集,确保没有除法指令。

很多电话

不幸的是,索引计算仍然会在大部分时间使用建议的解决方案,请参阅 generated assembly .如果您一次处理多个连续的 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
}
}
}

这样,您平均需要对每个排列进行一次随机播放(请参阅 assembly),这似乎接近完美。

代码和结果

这里是 full working code所有解决方案。

请注意,查找表的生成并不容易完全解释,并且相应的代码相当大(并且充满了令人讨厌的细节)。

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 倍.

关于c++ - 字节数组置换 SSE 优化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41501522/

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