这个功能是英特尔SIMD的理想选择吗?

时间:2014-03-06 08:10:13

标签: c++ c optimization 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;
        }

3 个答案:

答案 0 :(得分:5)

我不确定这是否是您问题的答案,也不确定这会大大加快您的代码速度,但这是我的想法。由于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;
}

对于需要对齐的架构,需要在第一个循环之前进行检查。

答案 1 :(得分:4)

您可以使用_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;
}

由于我在这台电脑上没有64 OS,我不能进行适当的性能测试。 但是,分析运行给出了:

  • 天真循环:30.44
  • 64位整数:15.22(在32位操作系统上)
  • SSE impl:5.21

所以SSE版本比朴素循环版本快很多。我希望64位版本在64位系统上表现更好 - SSE和64位版本之间可能差别不大。

答案 2 :(得分:2)

我尝试了几种解决问题的方法:基于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

所有解决方案的完整代码以及测试均可用here