此函数是英特尔SIMD的理想候选函数吗
Is this function a good candidate for SIMD on Intel?
我正在尝试优化以下函数(稍微简化了一点,但这是我的程序花费大量时间的循环):
int f(int len, unsigned char *p) {
int i = 0;
while (i < len && p[i] >= 32 && p[i] <= 127) {
i++;
}
return i;
}
我认为它可以用矢量指令进行优化,但从一点研究来看,SSE似乎不适合在字节级别工作。此程序仅针对OSX上的64位Intel CPU。有没有一个我看不到的聪明的小把戏可以让我一次处理64位?带有-O3的llvm没有做任何巧妙的优化。
更新:
SIMD代码在我的基准测试中通常是最快的(取决于输入的大小),但出于某种原因,使用SIMD的应用程序总体上比使用天真的代码或比特游戏慢。对于上下文,应用程序是在终端模拟器的输入流中查找ASCII字符串的子序列的长度。ASCII字符串得到特殊的"快速路径"处理。我只能把一个答案标记为正确,但两个都很棒。我确实做了一个小的改进,通过这样做删除了if语句:
while (i < len - 8) {
uint64_t bytes = *(uint64_t *)(p + i);
uint64_t middleBits = bytes & 0x6060606060606060;
uint64_t highBits = bytes & 0x8080808080808080;
middleBits |= (middleBits >> 1);
middleBits &= ~(highBits >> 2);
if ((middleBits & 0x2020202020202020) != 0x2020202020202020) {
break;
}
i += 8;
}
我不确定这是否是你的问题的答案,也不知道这是否会大大加快你的代码,但这是我脑海中的一个想法。由于32等于2^5,如果一个字节在32和128之间,则必须设置第6或第7位,并清除第8位。你可以将测试扩展到64位整数,给我一个代码,比如:
// check whether each byte is in range 32 - 128.
unsigned bytesInRange(unsigned long long x) {
unsigned long long y, z;
if ((x & 0x8080808080808080LL) != 0) return(0);
y = x >> 1;
z = x | y;
if ((z & 0x2020202020202020LL) == 0x2020202020202020LL) return(1);
return(0);
}
int f(int len, unsigned char *p) {
int i = 0;
int len8 = len / 8;
unsigned long long *q = (unsigned long long *) p;
while (i < len8 && bytesInRange(q[i])) {
i++;
}
i = i * 8;
while (i < len && p[i] >= 32 && p[i] <= 127) {
i++;
}
return i;
}
对于需要对齐的体系结构,需要在第一次循环之前进行检查。
您可以使用_mm_cmplt_epi8和_mm_cmpgt_epi7(msvc内部函数)对比较进行矢量化。
然后,您可以对比较结果进行"与"运算的结果使用movemask。如果movemask的结果是0xFFFF,则所有比较都通过。否则,您需要运行尾循环来找出未通过测试的正确位置。你可以从面具中看出这一点,但根据"len"的值,这可能不值得付出努力。
如果"len"不是16的倍数,则还需要尾部的原始未乘循环。它可能更快,也可能不更快——你需要对它进行分析才能确定。
废弃它-比较对有符号的值进行操作,它不起作用。。
下面是工作版本。
union UmmU8 {
__m128i mm_;
struct {
unsigned char u8_;
};
};
int f(int len, unsigned char *p) {
int i = 0;
__m128i A;
__m128i B;
__m128i C;
UmmU8* pu = (UmmU8*)p;
int const len16 = len / 16;
while (i < len16) {
A = pu[i].mm_;
B = _mm_slli_epi32(A, 1);
C = _mm_slli_epi32(A, 2);
B = _mm_or_si128(B, C);
A = _mm_andnot_si128(A, B);
int mask = _mm_movemask_epi8(A);
if (mask == 0xFFFF) {
++i;
}
else {
if (mask == 0) {
return i * 16;
}
break;
}
}
i *= 16;
while (i < len && p[i] >= 32 && p[i] <= 127) {
i++;
}
return i;
}
由于我在这台电脑上没有64操作系统,我无法进行适当的性能测试。然而,分析运行给出:
- 天真循环:30.44
- 64位整数:15.22(在32位操作系统上)
- SSE impl:5.21
因此SSE版本比naive循环版本快得多。我预计64位版本在64位系统上的性能会更好——SSE和64位版本之间可能没有什么区别。
我尝试了几种方法来解决这个问题:基于SSE2和SSE4.2。SSE4.2中的字符串操作相当慢,SSE2版本很容易胜过它们。请注意,一般来说,最佳解决方案在很大程度上取决于预期答案的平均大小。
以下是answer <= 400
性能最好的解决方案之一:
//SSE2 vectorization by stgatilov: no unrolling, fast BSF tail
int CommonAsciiLength_sse2_end(int len, unsigned char *p) {
const __m128i *ptr = (const __m128i *)p;
int blocks = len >> 4;
int cnt;
for (cnt = 0; cnt < blocks; cnt++) {
__m128i mask = _mm_cmplt_epi8(ptr[cnt], _mm_set1_epi8(32));
int val = _mm_movemask_epi8(mask);
if (val)
return 16 * cnt + __builtin_ctz(val);
}
__m128i mask = _mm_cmplt_epi8(ptr[cnt], _mm_set1_epi8(32));
int val = _mm_movemask_epi8(mask);
val |= -(1 << (len - 16 * cnt));
return 16 * cnt + __builtin_ctz(val);
}
请注意,对于较大的答案,此解决方案将从展开中进一步受益。
以下是不同解决方案和不同答案长度的一些时间安排。在常春藤大桥上测量。请注意,只有在一次跑步中比较计时才有意义,在不同平均值的跑步之间进行比较。答案是不正确的。
All checked.
Average answer = 7.0
Time = 4.879 (1884680192) original
Time = 6.021 (1884680192) bitmask
Time = 5.205 (1884680192) Pete
Time = 5.094 (1884680192) sse2
Time = 5.301 (1884680192) sse2_x4
Time = 1.603 (1884680192) sse42
Time = 1.235 (1884680192) sse2_end
Time = 2.319 (1884680192) sse2_x4_end
=========================================
All checked.
Average answer = 47.0
Time = 5.825 (-1867343006) original
Time = 4.792 (-1867343006) bitmask
Time = 4.490 (-1867343006) Pete
Time = 4.327 (-1867343006) sse2
Time = 5.260 (-1867343006) sse2_x4
Time = 3.347 (-1867343006) sse42
Time = 2.505 (-1867343006) sse2_end
Time = 3.008 (-1867343006) sse2_x4_end
=========================================
All checked.
Average answer = 151.4
Time = 4.372 (-2086294174) original
Time = 2.150 (-2086294174) bitmask
Time = 1.662 (-2086294174) Pete
Time = 1.492 (-2086294174) sse2
Time = 2.249 (-2086294174) sse2_x4
Time = 1.649 (-2086294174) sse42
Time = 0.986 (-2086294174) sse2_end
Time = 1.398 (-2086294174) sse2_x4_end
=========================================
All checked.
Average answer = 426.8
Time = 3.772 (1814680269) original
Time = 1.320 (1814680269) bitmask
Time = 0.830 (1814680269) Pete
Time = 0.692 (1814680269) sse2
Time = 0.870 (1814680269) sse2_x4
Time = 1.186 (1814680269) sse42
Time = 0.531 (1814680269) sse2_end
Time = 0.573 (1814680269) sse2_x4_end
=========================================
All checked.
Average answer = 1083.4
Time = 2.788 (358018991) original
Time = 0.819 (358018991) bitmask
Time = 0.443 (358018991) Pete
Time = 0.344 (358018991) sse2
Time = 0.347 (358018991) sse2_x4
Time = 0.813 (358018991) sse42
Time = 0.297 (358018991) sse2_end
Time = 0.256 (358018991) sse2_x4_end
这里提供了所有解决方案的完整代码以及测试。
- 候选构造函数(隐式复制构造函数)不可行:第一个参数需要 l 值
- 如果函数采用指向类的指针,则函数将完全脱离候选列表
- 候选函数不可行:没有从 std::vector<derived> 到 std::vector <base>的已知转换
- C++ - 候选函数不可行:没有从"结构"到"结构(&)"的已知转换
- C++ 候选构造函数不可行:没有已知的转换
- 错误:候选函数不可行:'this'参数具有类型 'const' 但方法未标记为 const
- 输入法管理器函数 - 平假名到汉字候选列表 c++ covnersion 的正确调用顺序
- 此函数是英特尔SIMD的理想候选函数吗
- 没有可调用的匹配函数(构造函数),候选函数不同于对给定类型的引用
- C++函数指针类型与 Linux 和 VMS 上的候选指针类型不兼容
- 错误:调用"反向"没有匹配函数:候选模板被忽略:推断出参数的冲突类型
- 虚拟函数可以作为RVO(返回值优化)的候选函数吗
- 我如何确保在有多个候选函数的情况下调用正确的函数
- 错误:没有匹配的函数调用'构造函数'注意:候选函数是:
- c++:没有可调用的匹配函数,但候选函数具有完全相同的签名
- 候选函数不可行:第一个参数('const Node *')将失去常量限定符
- 一组候选函数
- C++编译错误只有一个候选函数
- 对函数的调用在C++中是模棱两可的.候选函数是原型和函数本身
- 分而治之-C/C++库函数和运算符是最理想的函数和运算符吗