我正在尝试有效地执行以下任务:
INPUT VALUE: 01101011
MASK: 00110010
MASK RESULT: --10--1-
AGGREGATED: 00000101
我希望这个例子能清楚地解释我想要达到的目标。以非天真的方式做到这一点的最佳方法是什么?
我正在尝试有效地执行以下任务:
INPUT VALUE: 01101011
MASK: 00110010
MASK RESULT: --10--1-
AGGREGATED: 00000101
我希望这个例子能清楚地解释我想要达到的目标。以非天真的方式做到这一点的最佳方法是什么?
这个操作叫做compress_right
or just compress
,在没有硬件支持的情况下实现起来是中规中矩的。Hacker's Delight "7–4 Compress, or Generalized Extract" 中实现此功能的非朴素代码是
unsigned compress(unsigned x, unsigned m) {
unsigned mk, mp, mv, t;
int i;
x = x & m; // Clear irrelevant bits.
mk = ~m << 1; // We will count 0's to right.
for (i = 0; i < 5; i++) {
mp = mk ^ (mk << 1); // Parallel suffix.
mp = mp ^ (mp << 2);
mp = mp ^ (mp << 4);
mp = mp ^ (mp << 8);
mp = mp ^ (mp << 16);
mv = mp & m; // Bits to move.
m = m ^ mv | (mv >> (1 << i)); // Compress m.
t = x & mv;
x = x ^ t | (t >> (1 << i)); // Compress x.
mk = mk & ~mp;
}
return x;
}
BMI2(在 Haswell 和更高版本中实现)将具有pext
此操作的指令。
如果掩码是一个常数(或者不是一个常数但重复使用多次),一个相对明显的优化是预先计算mv
循环期间的 5 个值。的计算mv
不依赖于x
,所以可以独立计算,像这样(真的和上面的算法一样)
mk = ~m << 1;
for (i = 0; i < 5; i++) {
mp = mk ^ (mk << 1);
mp = mp ^ (mp << 2);
mp = mp ^ (mp << 4);
mp = mp ^ (mp << 8);
mp = mp ^ (mp << 16);
mv = mp & m;
mask[i] = mv;
m = m ^ mv | (mv >> (1 << i));
mk = mk & ~mp;
}
看起来还是很复杂,但这里的一切都是常数,所以可以预先计算(如果编译器做不到,那么你可以,只需运行它,然后将结果粘贴到代码中)。代码的“真实部分”,实际上必须在运行时运行的代码是这样的:
x = x & m;
t = x & mask[0];
x = x ^ t | (t >> 1);
t = x & mask[1];
x = x ^ t | (t >> 2);
t = x & mask[2];
x = x ^ t | (t >> 4);
t = x & mask[3];
x = x ^ t | (t >> 8);
t = x & mask[4];
x = x ^ t | (t >> 16);
(这也在 Hacker's Delight 中,格式略有不同)
许多情况可以再简单一点,例如:
m = 0
,结果是0
。m = -1
,结果是x
。m = 1
,结果是x & 1
。m = ((1 << n) - 1) << k
,结果是(x >> k) & m
。m = 0x80000000
,结果是x >> 31
。m
是 2 的另一个幂,则结果是(x >> numberOfTrailingZeros(m)) & 1
m
是交替的,则可以使用“完美的 unshuffle 算法”。m
由几个“组”组成,则可以使用“位组移动”算法(即屏蔽一个组,将其移动到位(或首先移位,第二个屏蔽),或所有移位的组一起,尽管存在更复杂的方法) ,这可能是实践中最重要的情况。例如,您问题中的掩码将属于“位组移动”的情况,代码如下:
return ((x >> 1) & 1) | ((x >> 3) & 6);