收集位的最快方法(类似于std :: copy_if)

时间:2017-05-29 23:54:13

标签: c bitwise-operators

澄清一下,这就是收集比特意味着:(在这个问题的背景下)

size_t gather_bits(size_t source, size_t mask) {
    size_t result = 0, next_bit_index = 0;
    for (size_t i = 0; i < sizeof(size_t) * 8; i++)
        if ((mask >> i) & 1)
            result |= ((source >> i) & 1) << next_bit_index++;
    return result;
}

对于掩码中的每个第N个位,结果中的第N个位从源中设置,掩码中的第N个位的索引。 (result[mask_on_bit] = source[mask_bit_index]

我添加的代码段是最简单的实现,但遗憾的是,我找到的最快,我无法提供更好的功能。还有什么比这更快的吗?考虑mask是完全随机的(因此在掩码中搜索大量的0应该不会有太大的好处)

3 个答案:

答案 0 :(得分:3)

您可能需要考虑无分支解决方案,这通常可以在某些体系结构上提供显着的性能优势。像这样:

size_t gather_branchless( size_t source, size_t mask )
{
    size_t result = 0, select = 1;
    source &= mask;
    while( source != 0 )
    {
        int used = (mask & 1);
        result |= (source & select);
        select <<= used;
        source >>= !used;
        mask >>= 1;
    }
    return result;
}

除了循环终止测试外,这完全避免了任何分支。我使用数百万随机生成的值运行此方法的基准来比较时间。在使用Clang和完全优化编译的英特尔酷睿i7 2.9GHz上运行:

+--------------+-------------+
| solution     | approx time |
+--------------+-------------+
| txtechhelp   | 1500 ms     |
| yours        | 1400 ms     |
| SGeorgiades  | 1300 ms     |
| branchless   | 600 ms      |
+--------------+-------------+

精明的人可能会注意到,我的无分支版本会在没有剩余部分合并时提前终止。为了公平起见,我运行测试时始终为值和掩码设置最高位。这样做会在结果上再添加50毫秒。

所以你有它。无分支解决方案,至少在我测试过的英特尔架构上,运行速度是您的速度的两倍以上。另一个好处是,如果您想进一步优化大型数据集上的代码,它很容易转换为SIMD。

如果您想使用其他解决方案,可以see my benchmark online。请注意,它是用C ++编写的,而不是C.我的测试使用g++ -std=c++11 -O2。这与包含使用gcc -O2生成的目标函数的C对象文件链接。

答案 1 :(得分:1)

我认为这应该更快,因为它一次只能移位一位,而且个别计算(尤其是将该位掩盖为“结果”的计算)要简单得多。

size_t gather_bits(size_t source, size_t mask) {
    size_t result = 0, next_bit_mask = 1;
    while (value)
    {
        if (mask & 1)
        {
            if (source & 1)
                result |= next_bit_mask;
            next_bit_mask <<= 1;
        }
        mask >>= 1;
        source >>= 1;
    }
    return result;
}

根据@ paddy的建议更新以终止值达到零时...好的通话!

答案 2 :(得分:1)

您的问题和发布的代码有点令人困惑。

在你的问题中,你要求掩码中第N位的每一个“,结果中的第N位是从掩码中第N位索引的源设置的。”我理解为mask中的1中的每个位都将result中的位设置为source中位1的位在mask中设置。

举一个简单的例子,如果source106(二进制0110 1010)而mask43(二进制0010 1011),结果应为0010 1010(十进制42);最简单,最快捷的方法是使用按位AND,例如:

size_t gather_bits(size_t source, size_t mask)
{
    return (source & mask);
}

但是你发布的代码并不能解决你在问题中提出的问题(除非我误解了你的问题)。相反,在您发布的代码中,如果Nth中的mask位置位,则会将Nth位从source推送到result的LSB。< / p>

使用上面的示例,如果source106mask43,则结果为0000 1110

如果 是你的意图,那么你可以使用已知值的静态地图(而不是在每次迭代中推送位),例如:

// assumes a 64-bit architecture
static size_t masks[] = {
    1,2,4,8,16,32,64,128,256,512,1024,
    2048,4096,8192,16384,32768,65536,131072,
    262144,524288,1048576,2097152,4194304,
    8388608,16777216,33554432,67108864,134217728,
    268435456,536870912,1073741824,2147483648,
    4294967296,8589934592,17179869184,34359738368,68719476736,
    137438953472,274877906944,549755813888,1099511627776,
    2199023255552,4398046511104,8796093022208,17592186044416,
    35184372088832,70368744177664,140737488355328,281474976710656,
    562949953421312,1125899906842624,2251799813685248,4503599627370496,
    9007199254740992,18014398509481984,36028797018963968,72057594037927936,
    144115188075855872,288230376151711744,576460752303423488,1152921504606846976,
    2305843009213693952,4611686018427387904,0x8000000000000000
};

size_t gather_bits(size_t source, size_t mask)
{
    size_t result = 0, next_bit_index = 0;
    for (size_t i = 0; i < sizeof(size_t) * 8; ++i)
        if (mask & masks[i])
            result |= ((source & masks[i]) >> (i - next_bit_index++));
    return result;
}

这减少了按位移位和分支,但如果实际更快将取决于很多事情,并且需要在您的环境中进行更多测试。

希望可以提供帮助。