c – 这个功能是英特尔SIMD的理想选择吗?
我正在尝试优化以下函数(简化了一下,但它是我的程序花了很多时间的循环):
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位? llvm与-O3没有做任何聪明的优化. 更新: 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; } 解决方法
您可以使用_mm_cmplt_epi8和_mm_cmpgt_epi8(msvc intrinsics)对比较进行矢量化.
然后,您可以对ANDing比较结果的结果使用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; } 由于我在这台PC上没有64 OS,我不能进行适当的性能测试. >天真循环:30.44 所以SSE版本比朴素循环版本快得多.我希望64位版本在64位系统上表现更好 – SSE和64位版本之间可能差别不大. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |