【问题标题】:Mask and aggregate bits掩码和聚合位
【发布时间】:2013-05-25 15:55:33
【问题描述】:

我正在尝试有效地执行以下任务:

INPUT VALUE: 01101011
MASK:        00110010
MASK RESULT: --10--1-
AGGREGATED:  00000101

我希望这个例子能清楚地解释我想要达到的目标。以非天真的方式做到这一点的最佳方法是什么?

【问题讨论】:

    标签: bit-manipulation bitmask


    【解决方案1】:

    此操作称为compress_right 或仅称为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 &amp; 1
    • 如果m = ((1 &lt;&lt; n) - 1) &lt;&lt; k,则结果为(x &gt;&gt; k) &amp; m
    • 如果m = 0x80000000,则结果为x &gt;&gt; 31
    • 如果m 是另一个2 的幂,则结果是(x &gt;&gt; numberOfTrailingZeros(m)) &amp; 1
    • 如果m是交替的,可以使用“完美的unshuffle算法”。
    • 如果m 由几个“组”组成,则可以使用“位组移动”算法(即屏蔽一个组,将其移动到位(或先移位,再屏蔽),或将所有移位的组一起,虽然存在更复杂的方法),但这可能是实践中最重要的情况。
    • ...

    例如,您的问题中的掩码将属于“位组移动”的情况,代码如下:

    return ((x >> 1) & 1) | ((x >> 3) & 6);
    

    【讨论】:

    • @FilippoBistaffa 顺便说一句,如果掩码是常量(或循环常量),则可以大大优化。
    • 是的,在我的场景中它将是一个常量,但我认为这种优化是由编译器自动完成的。还是明确地这样做更好?
    • @FilippoBistaffa 可能,但我不会指望它,而且常量掩码的代码无论如何都更简单。我将在一般优化中进行编辑,但对于许多掩码,代码可以再次简单。据我所知,可靠地找到 any 给定掩码的 最佳 代码是一个悬而未决的问题(尽管已知许多特殊情况)。
    猜你喜欢
    • 1970-01-01
    • 2016-02-20
    • 1970-01-01
    • 2017-01-09
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    • 2012-10-10
    • 2015-12-13
    相关资源
    最近更新 更多