【问题标题】:Most Efficient Algorithm for Bit Reversal with undefined size of data [closed]具有未定义数据大小的位反转的最有效算法[关闭]
【发布时间】:2018-04-20 14:00:55
【问题描述】:

实现以下目标的最快算法是什么:

1010 0000 => 0000 0101

转换是从 MSB LSB 到 LSB MSB。所有位都必须反转,但困难的是数据的大小在 1 字节和 8 字节之间,我无法提前知道。这意味着我无法预测大小。帧到达时带有一个指示帧大小的字节。大小在 1 字节和 8 字节之间变化。

【问题讨论】:

  • This 可能会有所帮助
  • “什么是最好的” 定义最好的。最快的?最易读? ROM/RAM 内存消耗最少?
  • "最好是为了在最短的时间内得到正确的结果" --> 使用查找表。它会很大(2 ^ 64)。 IOWs,在最短的时间内做到最好并不是一个合理的标准。编程通常需要平衡相互竞争的目标。 VTC 过于宽泛/不清楚,无法提供高质量的答案。
  • @Ivan 如果数据长为 5 Byte 不行你可以试试

标签: c byte reverse


【解决方案1】:

最佳是一个任意术语,因为您没有指定代码量或性能是否是您的标准。

对于“快速”结果,您应该只使用查找表 (LUT) 来反转每个字节中的位,然后将字节转移到结果中。

// there are easy ways to generate this table instead of doing it yourself
unsigned char Reverse[256] = {
  0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 
  ...
  0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
};

int last = 3-1; // last element 
unsigned char in[8] = { 0x12, 0x34, 0x56 };
unsigned char out[8];

for (int index = 0; index <= last; index++) {
    // store bytes in reverse, reverse bits in each byte using LUT
    out[ last-index] = Reverse[ in[ index] ];
}

如果上述方法不太令人满意,有许多替代方法可以在此 Bit Twiddling Hacks 页面上执行位反转

【讨论】:

  • 使用一组宏初始化 LUT 会更安全,以避免虚假错误。
  • 这就是我评论generating the table的原因。我在答案中包含的链接也有合适的宏。
【解决方案2】:

可以通过使用位操作一次递增地移动多个位来执行此类操作。

32 位整数示例:

uint32_t reverse(uint32_t x, size_t len)
{
    assert(len > 0 && len <= 4);
    x = ((x & 0x55555555) <<  1) | ((x & 0xAAAAAAAA) >>  1);
    x = ((x & 0x33333333) <<  2) | ((x & 0xCCCCCCCC) >>  2);
    x = ((x & 0x0F0F0F0F) <<  4) | ((x & 0xF0F0F0F0) >>  4);
    x = ((x & 0x00FF00FF) <<  8) | ((x & 0xFF00FF00) >>  8);
    x = ((x & 0x0000FFFF) << 16) | ((x & 0xFFFF0000) >> 16);
    return x >> (32 - len * 8);
}

这应该是最快且缓存友好的实现。

要为自定义宽度整数实现类似功能,您需要能够构造具有“X 位超过 X 位集”的位掩码,例如1 后 1 位(每隔一个位),2 后 2 等等。其余的应该是不言自明的。

您可以在此基础上实现其他变体。如果您需要在运行时选择它们,最好始终使用最宽版本(64 位)以避免分支。

注意:GCC 能够在 return 语句之前的最后两行中识别字节交换操作,并且能够在 x86 上生成bswap,在 ARM 上生成rev。使用 64 位 CPU,这使得 64 位版本在速度上与上述版本相当。


显然,如果不参考一些论文/书籍/专利,您现在无法发布基于常用技术的算法。因此,我在这里发布了一个高度专利、受版权保护并以其他方式受到保护的通用版本。 请注意,在使用它之前你应该三思而后行(因为它是专利)

不幸的是,从这段代码生成的程序集完全是垃圾。只有 clang 能够以某种方式了解正在发生的事情并将其优化掉。

我几年前编写的原始实现是用 C++ 编写的,并且严重依赖模板来帮助编译器。最后,这是一个可怕而复杂的混乱,但可以为 8、16、32 和 64 整数生成许多面向位的算法(如果编译器支持它们,甚至更多)。尽管如此,这可以作为上述算法的描述。

此代码使用 64 位代码的单个实例为 8、16、32、64 位字实现反向算法。辅助函数 repeatselect 可以用作许多位算法的基础(尽管它们基本上会生成所需的位掩码)。

#include <assert.h>
#include <stdint.h>
#include <stdlib.h>

/**
 * @brief Mask with `n` bits set.
 */
static uint64_t bits(size_t n)
{
    return (((1ull << n) - 1) | (-uint64_t(n >= 64)));
}

static uint64_t do_repeat(uint64_t x, size_t w, size_t n)
{
    if (n == 0)
        return x;

    const size_t shift = w * (n - 1);
    return (x << shift) | do_repeat(x, w, n - 1);
}

/**
 * @brief Repeat pattern over 64-bit word.
 *
 * @code
 * assert(repeat(       0x1, 32) == 0x5555555555555555);
 * assert(repeat(       0x3, 16) == 0x3333333333333333);
 * assert(repeat(       0xF,  8) == 0x0F0F0F0F0F0F0F0F);
 * assert(repeat(      0xFF,  4) == 0x00FF00FF00FF00FF);
 * assert(repeat(    0xFFFF,  2) == 0x0000FFFF0000FFFF);
 * assert(repeat(0xFFFFFFFF,  1) == 0x00000000FFFFFFFF);

 * assert(repeat(       0x1, 16) == 0x1111111111111111);
 * assert(repeat(      0x12,  8) == 0x1212121212121212);
 * assert(repeat(    0x1234,  4) == 0x1234123412341234);
 * assert(repeat(0x12345678,  2) == 0x1234567812345678);
 * @endcode
 */
static uint64_t repeat(uint64_t x, size_t n)
{
    assert(n != 0);
    return do_repeat(x, 64 / n, n);
}

/**
 * @brief Selects `1 << n` bits over `1 << n` bits.
 *
 * @code
 * assert(select(0) == 0x5555555555555555);
 * assert(select(1) == 0x3333333333333333);
 * assert(select(2) == 0x0F0F0F0F0F0F0F0F);
 * assert(select(3) == 0x00FF00FF00FF00FF);
 * assert(select(4) == 0x0000FFFF0000FFFF);
 * assert(select(5) == 0x00000000FFFFFFFF);
 * @endcode
 */
static uint64_t select(size_t n)
{
    assert(n < 6);
    return repeat(bits(1 << n), 1 << (5 - n));
}

static uint64_t do_reverse(uint64_t x, size_t n)
{
    const size_t shift = (1ull << n);
    const uint64_t lo = select(n) << 0;
    const uint64_t hi = select(n) << shift;

    x = ((x & lo) << shift) | ((x & hi) >> shift);

    if (n == 0)
        return x;

    return do_reverse(x, n - 1);
}

uint64_t reverse64(uint64_t x)
{
    return do_reverse(x, 5);
}

uint32_t reverse32(uint32_t x)
{
    return do_reverse(x, 4);
}

uint16_t reverse16(uint32_t x)
{
    return do_reverse(x, 3);
}

uint8_t reverse8(uint8_t x)
{
    return do_reverse(x, 2);
}

int main()
{
    assert(repeat(       0x1, 32) == 0x5555555555555555);
    assert(repeat(       0x3, 16) == 0x3333333333333333);
    assert(repeat(       0xF,  8) == 0x0F0F0F0F0F0F0F0F);
    assert(repeat(      0xFF,  4) == 0x00FF00FF00FF00FF);
    assert(repeat(    0xFFFF,  2) == 0x0000FFFF0000FFFF);
    assert(repeat(0xFFFFFFFF,  1) == 0x00000000FFFFFFFF);

    assert(repeat(       0x1, 16) == 0x1111111111111111);
    assert(repeat(      0x12,  8) == 0x1212121212121212);
    assert(repeat(    0x1234,  4) == 0x1234123412341234);
    assert(repeat(0x12345678,  2) == 0x1234567812345678);

    assert(select(0) == 0x5555555555555555);
    assert(select(1) == 0x3333333333333333);
    assert(select(2) == 0x0F0F0F0F0F0F0F0F);
    assert(select(3) == 0x00FF00FF00FF00FF);
    assert(select(4) == 0x0000FFFF0000FFFF);
    assert(select(5) == 0x00000000FFFFFFFF);

    assert(reverse8 (              0xA5) == 0xA5);
    assert(reverse16(            0xFEA5) == 0xA57F);
    assert(reverse32(        0xFE0000A5) == 0xA500007F);
    assert(reverse64(0xFE00FE0000A500A5) == 0xA500A500007F007F);

    return 0;
}

【讨论】:

  • 未知位数,可以是 5 或 195
  • @PeterJ_01 你应该读到最后。
  • 您可能需要说明您的算法来源。否则你有被指控抄袭的风险。
  • 如果你这么说。我刚刚注意到几乎相同的算法可以在这里找到:stackoverflow.com/questions/746171/…
  • @Ivan 但是你呢?你最初是在哪里发表的?一些书或纸?
【解决方案3】:

可以如下,检查firstlast位状态,如果两个位状态(0或1)不同,切换两者。

unsigned int data = 0xa0;
for(int start = 0, end = 8*sizeof(data) - 1; start < end; start++, end--) {
        if(data>>start&1 != data>>end&1) { /*check last and 1st bit pos status */ 
                data = data ^ 1<<start;/*complimenting start bit */
                data = data ^ 1<<end;
        }
}

end = 8*sizeof(data) - 1 如果输入是 8 位长或 32 位长,则两者都适用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多