【问题标题】:What's a time efficient algorithm to copy unaligned bit arrays?什么是复制未对齐位数组的高效算法?
【发布时间】:2011-04-01 20:42:16
【问题描述】:

过去我不得不这样做很多次,但我从未对结果感到满意。

谁能建议一种将连续位数组从源复制到目标的快速方法,其中源和目标可能未在方便的处理器边界上对齐(右移)?

如果源和目标都没有对齐,问题可以很快变成只有其中一个没有对齐的问题(在第一个副本之后)。

作为一个起点,我的代码不可避免地最终看起来类似于以下内容(未经测试,忽略副作用,这只是一个即兴例子):

const char mask[8] = { 1, 3, 7, 15, 31, 63, 127, 255 };
/* Assume:
 * - destination is already zeroed,
 * - offsets are right shifts
 * - bits to copy is big (> 32 say)
 */
int bitarray_copy(char * src, int src_bit_offset, int src_bit_len,
                  char * dst, int dst_bit_offset) {
    if (src_bit_offset == dst_bit_offset) { /* Not very interesting */ 
    } else {
        int bit_diff_offset = src_bit_offset - dst_bit_offset; /* assume positive */
        int loop_count;
        char c;
        char mask_val = mask[bit_diff_offset];

        /* Get started, line up the destination. */
        c  = (*src++ << bit_diff_offset) | ((*src >> (8 - bit_diff_offset)) & mask_val);
        c &= mask[8-dst_bit_offset];

        *dst++ |= c;

        src_bit_len -= 8 - dst_bit_offset;
        loop_count = src_bit_len >> 3;

        while (--loop_count >= 0) 
            * dst ++ = (*src++ << bit_diff_offset) | ((*src >> (8 - bit_diff_offset)) & mask_val);

        /* Trailing tail copy etc ... */
        if (src_bit_len % 8) /* ... */
    }
}

(其实这个比我之前做的好,看起来还不错)

【问题讨论】:

  • struct(s) 与位字段一起使用并让编译器来做? :P
  • 这会如何改进?
  • 这些位域是否重叠?能否将问题转化为简单应用 memcpy 即可解决的问题? Visual C++ 上的 memcpy 是高度优化的 (/ARCH:SSE2),GCC 和朋友至少确保在复制大块之前到达段落边界。

标签: c copy bitarray


【解决方案1】:

这就是我最终要做的。 (编辑在 2014 年 8 月 21 日更改了一个位复制错误。)

#include <limits.h>
#include <string.h>
#include <stddef.h>

#define PREPARE_FIRST_COPY()                                      \
    do {                                                          \
    if (src_len >= (CHAR_BIT - dst_offset_modulo)) {              \
        *dst     &= reverse_mask[dst_offset_modulo];              \
        src_len -= CHAR_BIT - dst_offset_modulo;                  \
    } else {                                                      \
        *dst     &= reverse_mask[dst_offset_modulo]               \
              | reverse_mask_xor[dst_offset_modulo + src_len];    \
         c       &= reverse_mask[dst_offset_modulo + src_len];    \
        src_len = 0;                                              \
    } } while (0)


static void
bitarray_copy(const unsigned char *src_org, int src_offset, int src_len,
                    unsigned char *dst_org, int dst_offset)
{
    static const unsigned char mask[] =
        { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff };
    static const unsigned char reverse_mask[] =
        { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
    static const unsigned char reverse_mask_xor[] =
        { 0xff, 0x7f, 0x3f, 0x1f, 0x0f, 0x07, 0x03, 0x01, 0x00 };

    if (src_len) {
        const unsigned char *src;
              unsigned char *dst;
        int                  src_offset_modulo,
                             dst_offset_modulo;

        src = src_org + (src_offset / CHAR_BIT);
        dst = dst_org + (dst_offset / CHAR_BIT);

        src_offset_modulo = src_offset % CHAR_BIT;
        dst_offset_modulo = dst_offset % CHAR_BIT;

        if (src_offset_modulo == dst_offset_modulo) {
            int              byte_len;
            int              src_len_modulo;
            if (src_offset_modulo) {
                unsigned char   c;

                c = reverse_mask_xor[dst_offset_modulo]     & *src++;

                PREPARE_FIRST_COPY();
                *dst++ |= c;
            }

            byte_len = src_len / CHAR_BIT;
            src_len_modulo = src_len % CHAR_BIT;

            if (byte_len) {
                memcpy(dst, src, byte_len);
                src += byte_len;
                dst += byte_len;
            }
            if (src_len_modulo) {
                *dst     &= reverse_mask_xor[src_len_modulo];
                *dst |= reverse_mask[src_len_modulo]     & *src;
            }
        } else {
            int             bit_diff_ls,
                            bit_diff_rs;
            int             byte_len;
            int             src_len_modulo;
            unsigned char   c;
            /*
             * Begin: Line things up on destination. 
             */
            if (src_offset_modulo > dst_offset_modulo) {
                bit_diff_ls = src_offset_modulo - dst_offset_modulo;
                bit_diff_rs = CHAR_BIT - bit_diff_ls;

                c = *src++ << bit_diff_ls;
                c |= *src >> bit_diff_rs;
                c     &= reverse_mask_xor[dst_offset_modulo];
            } else {
                bit_diff_rs = dst_offset_modulo - src_offset_modulo;
                bit_diff_ls = CHAR_BIT - bit_diff_rs;

                c = *src >> bit_diff_rs     &
                    reverse_mask_xor[dst_offset_modulo];
            }
            PREPARE_FIRST_COPY();
            *dst++ |= c;

            /*
             * Middle: copy with only shifting the source. 
             */
            byte_len = src_len / CHAR_BIT;

            while (--byte_len >= 0) {
                c = *src++ << bit_diff_ls;
                c |= *src >> bit_diff_rs;
                *dst++ = c;
            }

            /*
             * End: copy the remaing bits; 
             */
            src_len_modulo = src_len % CHAR_BIT;
            if (src_len_modulo) {
                c = *src++ << bit_diff_ls;
                c |= *src >> bit_diff_rs;
                c     &= reverse_mask[src_len_modulo];

                *dst     &= reverse_mask_xor[src_len_modulo];
                *dst |= c;
            }
        }
    }
}

【讨论】:

  • +1 好帖子!我一直在寻找这个:您的解决方案是否适用于 32 位和 64 位操作系统?我还没有梳理你的代码,但是中间的 memcpy() 对我来说肯定是有意义的。
  • 它应该适用于任何具有 c 编译器的架构。它们只是 c 指针。
  • 太棒了!我会试试看 - tyvm。
  • 通过了我所有的单元测试并且运行速度非常快!只有两个较小的编译器警告:mask、mask_xor 未使用,您可以将签名更改为 void,因为没有返回任何内容。
  • 已确认:非常快并且可以在手臂上工作。非常感谢。
【解决方案2】:

您的内部循环获取两个字节的片段并将它们移动到目标字节。这几乎是最佳的。这里还有一些提示,不分先后:

  • 没有必要一次将自己限制为一个字节。使用您的平台允许的最大整数大小。这当然会使您的起始和尾随逻辑复杂化。
  • 如果您使用无符号字符或整数,您可能不需要在右移后屏蔽源的第二部分。这取决于您的编译器。
  • 如果您确实需要掩码,请确保您的编译器将表查找移到循环之外。如果不是,请将其复制到一个临时变量并使用它。

【讨论】:

  • 感谢 cmets。但我正在寻找算法建议。 (无论数据类型如何,掩码都是必需的。)
  • @Jamie,当我说“几乎最优”时,我的意思是你已经有了一个很好的算法。当然它不能比 O(n) 做得更好,所以剩下的就是减少常数乘数。至于需要掩码,我最熟悉 Microsoft Visual C++,它会在您右移无符号整数时在左侧加载零,因此不需要掩码。
  • 我收回我的面具评论。对不起。
  • 右移一个无符号值总是移入零——这是标准的。它是由实现定义的行为的签名值。
【解决方案3】:

最佳选择取决于目标平台。在一些没有桶形移位器的平台上,将整个向量向右或向左移动一位,n 次,对于 n src1 = *src++; src2 = (src1 shl shiftamount1) | (src2 shr shiftamount2); *dest++ = src2; src2 = *src++; src1 = (src2 shl shiftamount1) | (src1 shr shiftamount2); *dest++ = src1;

这应该有助于在 ARM 上非常有效地实现(如果寄存器可用于 src、dest、src1、src2、shiftamount1 和 shiftamount2,则每两个字 8 条指令。使用更多寄存器将允许通过多字更快地操作加载/存储指令。处理四个字就像(每行一条机器指令,除了前四行和最后四行一样,都是一条指令):

src0 = *src++; src1 = *src++; src2 = *src++; src3 = *src++; tmp = src0; src0 = src0 shr shiftamount1 src0 = src0 | src1 shl shiftamount2 src1 = src1 shr shiftamount1 src1 = src1 | src2 shl shiftamount2 src2 = src2 shr shiftamount1 src2 = src2 | src3 shl shiftamount2 src3 = src3 shr shiftamount1 src3 = src3 | tmp shl shiftamount2 *dest++ = src0; *dest++ = src1; *dest++ = src2; *dest++ = src3;

每旋转 16 个字节有 11 条指令。

【讨论】:

    【解决方案4】:

    您的解决方案看起来与我见过的大多数解决方案相似:基本上在开始和结束时做一些未对齐的工作,中间的主循环使用对齐的访问。如果您真的需要效率并在很长的比特流上执行此操作,我建议您在主循环中使用特定于架构的东西,例如 SSE2。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-09
      • 2021-08-30
      相关资源
      最近更新 更多