9

假设您有一个 uint64_t,并且只关心 uint64_t 中每个字节的高位。像这样:

uint32_t:0000 ... 1000 0000 1000 0000 1000 0000 1000 0000 ---> 0000 1111

有没有比以下更快的方法:

   return
   (
     ((x >> 56) & 128)+
     ((x >> 49) &  64)+
     ((x >> 42) &  32)+
     ((x >> 35) &  16)+
     ((x >> 28) &   8)+
     ((x >> 21) &   4)+
     ((x >> 14) &   2)+
     ((x >>  7) &   1)
   )

又名移动 x,屏蔽,并为每个字节添加正确的位?这将编译成很多程序集,我正在寻找一种更快的方法……我使用的机器最多只有 SSE2 指令,我找不到有用的 SIMD 操作。

谢谢您的帮助。

4

6 回答 6

11

正如我在评论中提到的,pmovmskb做你想做的事。以下是您可以使用它的方法:

MMX + SSE1:

movq mm0, input ; input can be r/m
pmovmskb output, mm0 ; output must be r

上交所2:

movq xmm0, input
pmovmskb output, xmm0

我寻找了新的方式

体重指数2:

mov rax, 0x8080808080808080
pext output, input, rax ; input must be r
于 2012-08-29T15:43:39.570 回答
10
return ((x & 0x8080808080808080) * 0x2040810204081) >> 56;

作品。& 选择要保留的位。将所有位相乘到最高有效字节,然后移位将它们移动到最低有效字节。由于大多数现代 CPU 上的乘法运算速度很快,因此应该不会比使用汇编慢很多。

于 2012-08-29T18:55:39.380 回答
5

以下是使用 SSE 内在函数的方法:

#include <xmmintrin.h>
#include <inttypes.h>
#include <stdio.h>

int main (void)
{
  uint64_t x
  = 0b0000000010000000000000001000000000000000100000000000000010000000;

  printf ("%x\n", _mm_movemask_pi8 ((__m64) x));
  return 0;
}

适用于:

gcc -msse
于 2012-08-29T15:56:43.443 回答
4

您不需要所有单独的逻辑与,您可以将其简化为:

x &= 0x8080808080808080;
return (x >>  7) | (x >> 14) | (x >> 21) | (x >> 28) |
       (x >> 35) | (x >> 42) | (x >> 49) | (x >> 56);

(假设函数返回类型是uint8_t)。

您还可以将其转换为展开的循环:

uint8_t r = 0;

x &= 0x8080808080808080;

x >>= 7; r |= x;
x >>= 7; r |= x;
x >>= 7; r |= x;
x >>= 7; r |= x;
x >>= 7; r |= x;
x >>= 7; r |= x;
x >>= 7; r |= x;
x >>= 7; r |= x;
return r;

我不确定哪个在实践中会表现更好,尽管我倾向于打赌第一个 - 第二个可能会产生更短的代码但具有很长的依赖链。

于 2012-08-29T15:34:03.733 回答
2

首先,您实际上并不需要这么多操作。您一次可以对多个位进行操作:

x = (x >> 7) & 0x0101010101010101; // 0x0101010101010101
x |= x >> 28;                      // 0x????????11111111
x |= x >> 14;                      // 0x????????????5555
x |= x >>  7;                      // 0x??????????????FF
return x & 0xFF;

另一种方法是使用模数进行横向加法。首先要注意的x % n是 base 中的数字之和n+1,所以如果n+12^k,则添加 k 位组。如果你从 t = (x >> 7) & 0x0101010101010101上面开始,你想对 7 位的组求和,这t % 127就是解决方案。但t%127仅适用于高达 126. 0x8080808080808080 的结果,以上任何内容都会给出不正确的结果。我已经尝试了一些更正,没有一个容易的。

尝试使用模数使我们处于可能只有前一个算法的最后一步的情况。我们想要的是保留两个不太重要的位,然后得到另一个的总和,按 14 分组。所以

ull t = (x & 0x8080808080808080) >> 7;
ull u = (t & 3) | (((t>>2) % 0x3FFF) << 2);
return (u | (u>>7)) & 0xFF;

但是 t>>2 是 t/4 并且 << 2 是乘以 4。如果我们有(a % b)*c == (a*c % b*c),那么(((t>>2) % 0x3FFF) << 2)就是(t & ~3) % 0xFFFC。但我们也有一个事实,即 a + b%c = (a+b)%c 如果它小于 c。所以我们有简单u = t % FFFC的 . 给予:

ull t = ((x & 0x8080808080808080) >> 7) % 0xFFFC;
return (t | (t>>7)) & 0xFF;
于 2012-08-29T16:18:22.710 回答
0

这似乎有效:

return (x & 0x8080808080808080) % 127;
于 2012-08-29T16:17:40.280 回答