如何将 32 字符(0/1)的序列转换为 32 位(uint32_t)?

Posted

技术标签:

【中文标题】如何将 32 字符(0/1)的序列转换为 32 位(uint32_t)?【英文标题】:How to convert a sequence of 32 char (0/1) to 32 bits (uint32_t)? 【发布时间】:2018-12-03 02:27:09 【问题描述】:

我有一个从文件中读取的 char 数组(通常是数千字节长),全部由 0 和 1 组成(不是 '0' 和 '1',在这种情况下我可以使用 strtoul)。我想将它们打包成单个位,从而将每个 32 个字符转换为单个 uint32_t。我应该写一个 32 部分的位移操作,还是有更理智的方法?

out[i/32] = 
    data[i] << 31 |
    data[i+1] << 30 |
    data[i+2] << 29 |
    data[i+3] << 28 |
    data[i+4] << 27 |
    data[i+5] << 26 |
    data[i+6] << 25 |
    data[i+7] << 24 |
    data[i+8] << 23 |
    data[i+9] << 22 |
    data[i+10] << 21 |
    data[i+11] << 20 |
    data[i+12] << 19 |
    data[i+13] << 18 |
    data[i+14] << 17 |
    data[i+15] << 16 |
    data[i+16] << 15 |
    data[i+17] << 14 |
    data[i+18] << 13 |
    data[i+19] << 12 |
    data[i+20] << 11 |
    data[i+21] << 10 |
    data[i+22] << 9 |
    data[i+23] << 8 |
    data[i+24] << 7 |
    data[i+25] << 6 |
    data[i+26] << 5 |
    data[i+27] << 4 |
    data[i+28] << 3 |
    data[i+29] << 2 |
    data[i+30] << 1 |
    data[i+31];

如果这个可怕的位移是运行时最快的,那么我将不得不坚持下去。

【问题讨论】:

是的,按位or 并在迭代每个字节时移位是我认为在这种情况下你能做的所有事情。 整个文件只有32字节长?或者您真的想对长字节流执行此操作? @BeeOnRope 这取决于光栅的大小。我在 40,000 字符长(相当于栅格中的一行)上对其进行测试。 How to create a byte out of 8 bool values (and vice versa)?, What's the fastest way to pack 32 0/1 values into the bits of a single 32-bit variable?, How to efficiently convert an 8-bit bitmap to array of 0/1 integers with x86 SIMD What's the fastest way to pack 32 0/1 values into the bits of a single 32-bit variable?的可能重复 【参考方案1】:

受限于x86平台,可以使用PEXT指令。它是较新处理器上 BMI2 指令集扩展的一部分。

连续使用 32 位指令,然后通过移位将结果合并为一个值。

这可能是 Intel 处理器上的最佳方法,但缺点是该指令在 AMD Ryzen 上很慢。

【讨论】:

【参考方案2】:

如果您不需要输出位以与输入字节完全相同的顺序出现,但如果它们可以以特定方式“交错”,那么实现此目的的一种快速且可移植的方法是采用8 个 8 字节的块(总共 64 字节),并将所有 LSB 组合成一个 8 字节的值。

类似:

uint32_t extract_lsbs2(uint8_t (&input)[32]) 
  uint32_t t0, t1, t2, t3, t4, t5, t6, t7;
  memcpy(&t0, input + 0 * 4, 4);
  memcpy(&t1, input + 1 * 4, 4);
  memcpy(&t2, input + 2 * 4, 4);
  memcpy(&t3, input + 3 * 4, 4);
  memcpy(&t4, input + 4 * 4, 4);
  memcpy(&t5, input + 5 * 4, 4);
  memcpy(&t6, input + 6 * 4, 4);
  memcpy(&t7, input + 7 * 4, 4);

  return 
    (t0 << 0) |
    (t1 << 1) |
    (t2 << 2) |
    (t3 << 3) |
    (t4 << 4) |
    (t5 << 5) |
    (t6 << 6) |
    (t7 << 7);

这会在most compilers 上生成“不可怕,不是很好”的代码。

如果您使用 uint64_t 而不是 uint32_t,在 64 位平台上通常会快两倍(假设您有超过 32 个总字节要转换)。

使用 SIMD,您可以通过两条指令轻松矢量化整个操作(对于 AVX2,但任何 x86 SIMD ISA 都可以):比较和 pmovmskb

【讨论】:

我可以想象pandpmaddubswphaddwphaddd 的序列,它可以兼容比 AVX2 更多的机器,而且速度可能相当快... @IwillnotexistIdonotexist - 我没有想到 AVX2 中的任何特殊指令,只是碰巧 OP 建议的 32 个字节只需要 1 条 AVX 指令。你可以用 SSE 做到这一点,速度只有一半。需要明确的是,我正在考虑一个带有标量存储的 cmpeq; movmskb 序列。这应该实现一个周期接近 64 位的输出(将两个 32 位 reg 组合成一个 64 位以节省存储带宽),这意味着每个周期接近 64 字节的输入。我认为使用全 SIMD 解决方案很难击败它?【参考方案3】:

位移是解决此问题的最简单方法。最好编写反映您实际操作的代码,而不是尝试进行微优化。

所以你想要这样的东西:

char bits[32];
// populate bits
uint32_t value = 0;
for (int i=0; i<32; i++) 
    value |= (uint32_t)(bits[i] & 1) << i;

【讨论】:

谢谢。请看看我的编辑。循环不是比单个操作效率低吗?还是两者对处理器都一样? @Rodrigo 许多编译器会执行循环展开作为优化。除非您确定了特定的瓶颈,否则不要担心这种级别的手动优化。

以上是关于如何将 32 字符(0/1)的序列转换为 32 位(uint32_t)?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 64 位 void 指针转换为 32 位 void 指针?

如何将 32 位 *.lib 文件转换为 64 位 *.a 文件

如何将 8 位 OpenCV IplImage* 转换为 32 位 IplImage*?

将短字符串转换为32位整数的最有效方法是什么?

NEON:如何将 128 位 ARGB 转换为具有饱和度的 32 位 ARGB?

如何使用 avx2 将 24 位 rgb 转换为 32 位?