C++ 高阶位-取它们并将一个uint64\u t转换成一个uint8\u t
假设您有一个uint64,只关心uint64中每个字节的高阶位。像这样: uint32\t: 0000 ... 1000万1000万1000万-->0000 1111 有没有比以下更快的方法:C++ 高阶位-取它们并将一个uint64\u t转换成一个uint8\u t,c++,c,assembly,bit-manipulation,C++,C,Assembly,Bit Manipulation,假设您有一个uint64,只关心uint64中每个字节的高阶位。像这样: uint32\t: 0000 ... 1000万1000万1000万-->0000 1111 有没有比以下更快的方法: return ( ((x >> 56) & 128)+ ((x >> 49) & 64)+ ((x >> 42) & 32)+ ((x >> 35) & 16)+
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 ops
感谢您的帮助。您不需要所有单独的逻辑AND,您可以将其简化为:
x &= 0x8080808080808080;
return (x >> 7) | (x >> 14) | (x >> 21) | (x >> 28) |
(x >> 35) | (x >> 42) | (x >> 49) | (x >> 56);
(假设函数返回类型为uint8\u 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;
我不确定哪一个在实践中会表现得更好,尽管我倾向于打赌第一个-第二个可能会产生较短的代码,但依赖链较长。正如我在评论中提到的,
pmovskb
满足您的需要。以下是您可以如何使用它:
MMX+SSE1:
movq mm0, input ; input can be r/m
pmovmskb output, mm0 ; output must be r
SSE2:
我以新的方式查找
BMI2:
下面是如何使用SSE intrinsics实现这一点:
#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;
}
这似乎有效:
return (x & 0x8080808080808080) % 127;
首先,你真的不需要这么多手术。一次可以对多个位执行操作:
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
是基n+1
中的数字之和,因此如果n+1
是2^k
,则添加了k个位的组。如果你从
t=(x>>7)&0x0101010101
如上所述,您希望对7位的组求和,因此t%127
将是解决方案。但是t%127
仅适用于126的结果。0x8080及以上任何内容将给出不正确的结果。我试过一些修正,没有一个是容易的
试图用模来把我们置于这样一种情况,即前面算法的最后一步是可能的。我们想要的是保留两个低有效位,然后将另一个的总和按14分组。所以
ull t = (x & 0x8080808080808080) >> 7;
ull u = (t & 3) | (((t>>2) % 0x3FFF) << 2);
return (u | (u>>7)) & 0xFF;
工作。&选择要保留的位。将所有位相乘到最高有效字节,移位将它们移动到最低有效字节。因为乘法在大多数现代CPU上都很快,所以它不应该比使用汇编慢太多。您可以重新解释单个字节,循环遍历它们并屏蔽单个位。不知道这是否更快,但也许编译器可以更好地优化它。也许你可以先用
0x8080
屏蔽,然后乘以一个特定的常数,将这些位放在更方便的位置,也许可以在查找表中使用。你需要结果吗,即8位序列作为一个数字?或者只检查HO位是否为1
就足够了?是的,pmovmskb
正是您想要的。IIRC在AVX2中会有一条整数指令,你可以用它做同样的事情(收集位,忘记助记符)。@AndyRoss我正在写它,花了一段时间,因为我真的想把新指令放在那里:)而百万美元的问题是:gcc-msse
是否为此代码生成pmovskb
您可能希望将该常量限定为ULL
,这样编译器就不会试图对有符号的值耍花招了。@MarkB:在C++11中这不是必需的。我很确定ULL在C99中也不是必需的-因为x
是无符号的,即使该常量有符号,它也会被提升为无符号的(即使常数的类型比uint64\t
宽,这也是正确的。)如果添加正确的内联asm(具有适当的约束),则+1使用此方法生成最佳代码。@R..I会的,但我以前从未这样做过。我尽量不使用10英尺长的杆来触碰GCC。我查看了这些约束,并且,好吧,代码可能会在稍后出现。无论如何,可能是OK+1。如果我有时间研究如何做,我会添加它。这个asm没有简单的内在特性吗?@rubenvb you te告诉我。我从来没有想过如何从带有内部函数的寄存器中MOVQ
。如果您设置了第一个位,因此需要回答>=128,则不会。这实际上可能比pmovmsk
快,后者是一条非常慢的指令。@drhirsch 2周期延迟(AMD K10上的3)Core2上的吞吐量为1,一点也不差,甚至这里的乘法也更差。
return (x & 0x8080808080808080) % 127;
x = (x >> 7) & 0x0101010101010101; // 0x0101010101010101
x |= x >> 28; // 0x????????11111111
x |= x >> 14; // 0x????????????5555
x |= x >> 7; // 0x??????????????FF
return x & 0xFF;
ull t = (x & 0x8080808080808080) >> 7;
ull u = (t & 3) | (((t>>2) % 0x3FFF) << 2);
return (u | (u>>7)) & 0xFF;
ull t = ((x & 0x8080808080808080) >> 7) % 0xFFFC;
return (t | (t>>7)) & 0xFF;
return ((x & 0x8080808080808080) * 0x2040810204081) >> 56;