【问题标题】:Portable efficient alternative to PDEP without using BMI2?不使用 BMI2 的便携式高效替代 PDEP?
【发布时间】:2016-08-14 03:39:04
【问题描述】:

英特尔的位操作指令集 2 (BMI2) 中的 parallel deposit instruction (PDEP) 文档描述了该指令的以下串行实现(类 C 伪代码):

U64 _pdep_u64(U64 val, U64 mask) {
  U64 res = 0;
  for (U64 bb = 1; mask; bb += bb) {
    if (val & bb)
      res |= mask & -mask;
    mask &= mask - 1;
  }
  return res;
}

See also Intel's pdep insn ref manual entry.

这个算法是O(n),其中n是mask中设置的位数,显然最坏的情况是O(k),其中k是mask中的总位数。

是否有可能更有效的最坏情况算法?

是否有可能制作一个更快的版本,假设val 最多设置一个位,即等于0 或等于1<<r 对于r 从0 到63 的某个值?

【问题讨论】:

  • Henry Warren,“Hacker's Delight”,第 2 版,第 7-5 章给出了一个用于一般 32 位 deposit 操作的并行后缀算法,并声称它需要大约160 条指令(具体数量取决于处理器指令集的具体情况)。如果我正确理解您关于 1 位存款的特殊情况的第二个问题,归结为快速隔离 mask 的第 r 位。
  • 对于您的1比特存款的特殊情况,是否会提前知道r,或者我们必须先通过检查val找到r,然后再隔离r -mask中的第 1 位?
  • r 如果还不知道,可以很容易地找到它。假设它是已知的。

标签: algorithm assembly x86 bit-manipulation bmi


【解决方案1】:

问题的第二部分,关于 1 位存款的特殊情况,需要两个步骤。第一步,我们需要确定val中单个1位的位索引r,以在val为零的情况下做出适当的响应。这可以通过 POSIX 函数 ffs 轻松完成,或者如果 r 通过其他方式已知,正如 cmets 中的询问者所暗示的那样。在第二步中,我们需要识别maskr-th 1 位的位索引i(如果存在)。然后我们可以将valr-th 位存入i 位。

mask 中查找r-th 1 位的索引的一种方法是使用基于二进制分区的经典population count 算法对1 位进行计数,并记录所有中间组-明智的位数。然后,我们对记录的位计数数据执行二进制搜索,以确定所需位的位置。

以下C-代码使用 64 位数据演示了这一点。这实际上是否比迭代方法快在很大程度上取决于maskval 的典型值。

#include <stdint.h>

/* Find the index of the n-th 1-bit in mask, n >= 0
   The index of the least significant bit is 0 
   Return -1 if there is no such bit
*/
int find_nth_set_bit (uint64_t mask, int n)
{
    int t, i = n, r = 0;
    const uint64_t m1 = 0x5555555555555555ULL; // even bits
    const uint64_t m2 = 0x3333333333333333ULL; // even 2-bit groups
    const uint64_t m4 = 0x0f0f0f0f0f0f0f0fULL; // even nibbles
    const uint64_t m8 = 0x00ff00ff00ff00ffULL; // even bytes
    uint64_t c1 = mask;
    uint64_t c2 = c1 - ((c1 >> 1) & m1);
    uint64_t c4 = ((c2 >> 2) & m2) + (c2 & m2);
    uint64_t c8 = ((c4 >> 4) + c4) & m4;
    uint64_t c16 = ((c8 >> 8) + c8) & m8;
    uint64_t c32 = (c16 >> 16) + c16;
    int c64 = (int)(((c32 >> 32) + c32) & 0x7f);
    t = (c32    ) & 0x3f; if (i >= t) { r += 32; i -= t; }
    t = (c16>> r) & 0x1f; if (i >= t) { r += 16; i -= t; }
    t = (c8 >> r) & 0x0f; if (i >= t) { r +=  8; i -= t; }
    t = (c4 >> r) & 0x07; if (i >= t) { r +=  4; i -= t; }
    t = (c2 >> r) & 0x03; if (i >= t) { r +=  2; i -= t; }
    t = (c1 >> r) & 0x01; if (i >= t) { r +=  1;         }
    if (n >= c64) r = -1;
    return r; 
}

/* val is either zero or has a single 1-bit.
   Return -1 if val is zero, otherwise the index of the 1-bit
   The index of the least significant bit is 0
*/
int find_bit_index (uint64_t val)
{
    return ffsll (val) - 1;
}

uint64_t deposit_single_bit (uint64_t val, uint64_t mask)
{
    uint64_t res = (uint64_t)0;
    int r = find_bit_index (val);
    if (r >= 0) {
        int i = find_nth_set_bit (mask, r);
        if (i >= 0) res = (uint64_t)1 << i;
    } 
    return res;
}

【讨论】:

  • 这很酷。 find_nth_set_bit 中有一些神奇的数字,但是......所以我不确定将它扩展到超过 32 位的整数......比如 64、128 甚至 256 位。
  • @markt1964 我已将代码更改为 64 位实现,并使用命名掩码使这些掩码的作用更加清晰。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-04
相关资源
最近更新 更多