饱和减数 - AVX或SSE4.2

Cri*_*les 7 c optimization gcc sse avx

我正在改进程序(C)的性能,我无法获得更好的执行时间来改善最"昂贵"的循环.

如果元素大于零,我必须从unsigned long int数组的每个元素中减去1.

循环是:

unsigned long int * WorkerDataTime;
...
for (WorkerID=0;WorkerID<WorkersON;++WorkerID){
    if(WorkerDataTime[WorkerID] > 0) WorkerDataTime[WorkerID]-=1;
}
Run Code Online (Sandbox Code Playgroud)

我试试这个:

for (WorkerID=0;WorkerID<WorkersON;++WorkerID){
    int rest = WorkerDataTime[WorkerID] > 0;
    WorkerDataTime[WorkerID] = WorkerDataTime[WorkerID] - rest;
}
Run Code Online (Sandbox Code Playgroud)

但执行时间类似.

问题:是否有任何内向指令(SSE4.2,AVX ......)直接执行此操作?(我正在使用gcc 4.8.2)

我知道有可能使用char或short元素.(_mm_subs_epi8和_mm_subs_epi16)我不能使用AVX2.

谢谢.

Nil*_*nck 8

使用SSE4可以使用三条指令.这是一个处理整个数组的代码,递减所有非零的无符号整数:

void clampedDecrement_SSE (__m128i * data, size_t count)
{
  // processes 2 elements each, no checks for alignment done.
  // count must be multiple of 2.

  size_t i;
  count /= 2;

  __m128i zero = _mm_set1_epi32(0);
  __m128i ones = _mm_set1_epi32(~0);

  for (i=0; i<count; i++)
  {
    __m128i values, mask;

    // load 2 64 bit integers:
    values = _mm_load_si128 (data);

    // compare against zero. Gives either 0 or ~0 (on match)
    mask   = _mm_cmpeq_epi64 (values, zero);

    // negate above mask. Yields -1 for all non zero elements, 0 otherwise:
    mask   = _mm_xor_si128(mask, ones);

    // now just add the mask for saturated unsigned decrement operation:
    values = _mm_add_epi64(values, mask);

    // and store the result back to memory:
   _mm_store_si128(data,values);
   data++;
  }
}
Run Code Online (Sandbox Code Playgroud)

使用AVX2,我们可以对此进行改进,并在时间处理4个元素:

void clampedDecrement (__m256i * data, size_t count)
{
  // processes 4 elements each, no checks for alignment done.
  // count must be multiple of 4.

  size_t i;
  count /= 4;

  // we need some constants:
  __m256i zero = _mm256_set1_epi32(0);
  __m256i ones = _mm256_set1_epi32(~0);

  for (i=0; i<count; i++)
  {
    __m256i values, mask;

    // load 4 64 bit integers:
    values = _mm256_load_si256 (data);

    // compare against zero. Gives either 0 or ~0 (on match)
    mask   = _mm256_cmpeq_epi64 (values, zero);

    // negate above mask. Yields -1 for all non zero elements, 0 otherwise:
    mask   = _mm256_xor_si256(mask, ones);

    // now just add the mask for saturated unsigned decrement operation:
    values = _mm256_add_epi64(values, mask);

    // and store the result back to memory:
   _mm256_store_si256(data,values);
   data++;
  }
}
Run Code Online (Sandbox Code Playgroud)

编辑:添加了SSE代码版本.

  • @CristianMorales,Nils比我更应该得到公认的答案.他在我面前提出了一个更通用的解决方案(针对SSE4.1)(XOP不如SSE4.1常见).我应该已经意识到,对于无符号,`x> 0`与`x!= 0`相同. (2认同)

Z b*_*son 5

除非您的CPU具有XOP,否则没有有效的方法来比较无符号的64位整数.

我从Agner Fog的Vector Class Library中删除了以下内容.这显示了如何比较无符号的64位整数.

static inline Vec2qb operator > (Vec2uq const & a, Vec2uq const & b) {
#ifdef __XOP__  // AMD XOP instruction set
    return Vec2q(_mm_comgt_epu64(a,b));
#else  // SSE2 instruction set
    __m128i sign32  = _mm_set1_epi32(0x80000000);          // sign bit of each dword
    __m128i aflip   = _mm_xor_si128(a,sign32);             // a with sign bits flipped
    __m128i bflip   = _mm_xor_si128(b,sign32);             // b with sign bits flipped
    __m128i equal   = _mm_cmpeq_epi32(a,b);                // a == b, dwords
    __m128i bigger  = _mm_cmpgt_epi32(aflip,bflip);        // a > b, dwords
    __m128i biggerl = _mm_shuffle_epi32(bigger,0xA0);      // a > b, low dwords copied to high dwords
    __m128i eqbig   = _mm_and_si128(equal,biggerl);        // high part equal and low part bigger
    __m128i hibig   = _mm_or_si128(bigger,eqbig);          // high part bigger or high part equal and low part bigger
    __m128i big     = _mm_shuffle_epi32(hibig,0xF5);       // result copied to low part
    return  Vec2qb(Vec2q(big));
#endif
}
Run Code Online (Sandbox Code Playgroud)

因此,如果你的CPU支持XOP,你应该尝试编译,-mxop看看循环是否被矢量化.

编辑:如果GCC没有像你想要的那样向量化,你的CPU有XOP你可以做

for (WorkerID=0; WorkerID<WorkersON-1; workerID+=2){
    __m128i v = _mm_loadu_si128((__m128i*)&WorkerDataTime[workerID]);
    __m128i cmp = _mm_comgt_epu64(v, _mm_setzero_si128());
    v = _mm_add_epi64(v,cmp);
    _mm_storeu_si128((__m128i*)&WorkerDataTime[workerID], v);
}
for (;WorkerID<WorkersON;++WorkerID){
    if(WorkerDataTime[WorkerID] > 0) WorkerDataTime[WorkerID]-=1;
}
Run Code Online (Sandbox Code Playgroud)

编译-mxop并包含#include <x86intrin.h>.

编辑:正如Nils Pipenbrinck指出的,如果您没有XOP,您可以使用以下一条指令执行此操作_mm_xor_si128:

for (WorkerID=0; WorkerID<WorkersON-1; WorkerID+=2){
    __m128i v = _mm_loadu_si128((__m128i*)&WorkerDataTime[workerID]);
    __m128i mask = _mm_cmpeq_epi64(v,_mm_setzero_si128());
    mask = _mm_xor_si128(mask, _mm_set1_epi32(~0));
    v= _mm_add_epi64(v,mask);
    _mm_storeu_si128((__m128i*)&WorkerDataTime[workerID], v);
}
for (;WorkerID<WorkersON;++WorkerID){
    if(WorkerDataTime[WorkerID] > 0) WorkerDataTime[WorkerID]-=1;
}
Run Code Online (Sandbox Code Playgroud)

编辑:根据Stephen Canon的评论,我了解到有一种更有效的方法可以使用pcmpgtqSSE4.2 的指令比较一般的64位无符号整数:

__m128i a,b;
__m128i sign64 = _mm_set1_epi64x(0x8000000000000000L);
__m128i aflip = _mm_xor_si128(a, sign64);
__m128i bflip = _mm_xor_si128(b, sign64);
__m128i cmp = _mm_cmpgt_epi64(aflip,bflip);
Run Code Online (Sandbox Code Playgroud)