如何有效地解交织比特(逆莫顿)

时间:2011-02-05 19:48:42

标签: bit-manipulation z-order-curve

这个问题:How to de-interleave bits (UnMortonizing?)有一个很好的答案,可以提取莫顿数的两半中的一半(只是奇数位),但我需要一个解决方案来提取两个部分(奇数位和偶数位) )尽可能少的操作。

对于我的使用,我需要取一个32位的int并提取两个16位的整数,其中一个是偶数位,另一个是奇数位右移1位,例如

input,  z: 11101101 01010111 11011011 01101110

output, x: 11100001 10110111 // odd bits shifted right by 1
        y: 10111111 11011010 // even bits

似乎有很多解决方案使用带有幻数的移位和掩码来生成莫顿数(即交错比特),例如: Interleave bits by Binary Magic Numbers,但我还没有找到任何反向做法(即去交错)。

更新

在重新阅读Hacker's Delight中关于完美洗牌/洗牌的部分后,我找到了一些有用的例子,我改编如下:

// morton1 - extract even bits

uint32_t morton1(uint32_t x)
{
    x = x & 0x55555555;
    x = (x | (x >> 1)) & 0x33333333;
    x = (x | (x >> 2)) & 0x0F0F0F0F;
    x = (x | (x >> 4)) & 0x00FF00FF;
    x = (x | (x >> 8)) & 0x0000FFFF;
    return x;
}

// morton2 - extract odd and even bits

void morton2(uint32_t *x, uint32_t *y, uint32_t z)
{
    *x = morton1(z);
    *y = morton1(z >> 1);
}

我认为这仍然可以通过当前的标量形式和利用SIMD来改进,所以我仍然对更好的解决方案(标量或SIMD)感兴趣。

5 个答案:

答案 0 :(得分:11)

如果您的处理器有效处理64位整数,您可以合并操作...

int64 w = (z &0xAAAAAAAA)<<31 | (z &0x55555555 )
w = (w | (w >> 1)) & 0x3333333333333333;
w = (w | (w >> 2)) & 0x0F0F0F0F0F0F0F0F; 
...

答案 1 :(得分:5)

Intel Haswell及更高版本CPU的代码。您可以使用包含pext和pdep指令的BMI2指令集。这些可以(以及其他伟大的事情)用于建立你的功能。

#include <immintrin.h>
#include <stdint.h>

// on GCC, compile with option -mbmi2, requires Haswell or better.

uint64_t xy_to_morton (uint32_t x, uint32_t y)
{
    return _pdep_u32(x, 0x55555555) | _pdep_u32(y,0xaaaaaaaa);
}

uint64_t morton_to_xy (uint64_t m, uint32_t *x, uint32_t *y)
{
    *x = _pext_u64(m, 0x5555555555555555);
    *y = _pext_u64(m, 0xaaaaaaaaaaaaaaaa);
}

答案 2 :(得分:4)

如果有人在3d中使用morton代码,那么他需要每3位读一位,这里64位是我使用的函数:

uint64_t morton3(uint64_t x) {
    x = x & 0x9249249249249249;
    x = (x | (x >> 2))  & 0x30c30c30c30c30c3;
    x = (x | (x >> 4))  & 0xf00f00f00f00f00f;
    x = (x | (x >> 8))  & 0x00ff0000ff0000ff;
    x = (x | (x >> 16)) & 0xffff00000000ffff;
    x = (x | (x >> 32)) & 0x00000000ffffffff;
    return x;
}
uint64_t bits; 
uint64_t x = morton3(bits)
uint64_t y = morton3(bits>>1)
uint64_t z = morton3(bits>>2)

答案 3 :(得分:1)

如果你需要速度,你可以一次使用表查找进行一个字节转换(两个字节表更快但是更大)。程序在Delphi IDE下完成,但汇编程序/ algorithem是相同的。

const
  MortonTableLookup : array[byte] of byte = ($00, $01, $10, $11, $12, ... ;

procedure DeinterleaveBits(Input: cardinal);
//In: eax
//Out: dx = EvenBits; ax = OddBits;
asm
  movzx   ecx, al                                     //Use 0th byte
  mov     dl, byte ptr[MortonTableLookup + ecx]
//
  shr     eax, 8
  movzx   ecx, ah                                     //Use 2th byte
  mov     dh, byte ptr[MortonTableLookup + ecx]
//
  shl     edx, 16
  movzx   ecx, al                                     //Use 1th byte
  mov     dl, byte ptr[MortonTableLookup + ecx]
//
  shr     eax, 8
  movzx   ecx, ah                                     //Use 3th byte
  mov     dh, byte ptr[MortonTableLookup + ecx]
//
  mov     ecx, edx  
  and     ecx, $F0F0F0F0
  mov     eax, ecx
  rol     eax, 12
  or      eax, ecx

  rol     edx, 4
  and     edx, $F0F0F0F0
  mov     ecx, edx
  rol     ecx, 12
  or      edx, ecx
end;

答案 4 :(得分:1)

我不想限制为固定大小的整数并使用硬编码常量制作类似命令的列表,因此我开发了一个C ++ 11解决方案,该解决方案利用模板元编程生成函数和常量。使用-O3生成的汇编代码似乎与不使用BMI时一样严格:

andl    $0x55555555, %eax
movl    %eax, %ecx
shrl    %ecx
orl     %eax, %ecx
andl    $0x33333333, %ecx
movl    %ecx, %eax
shrl    $2, %eax
orl     %ecx, %eax
andl    $0xF0F0F0F, %eax
movl    %eax, %ecx
shrl    $4, %ecx
orl     %eax, %ecx
movzbl  %cl, %esi
shrl    $8, %ecx
andl    $0xFF00, %ecx
orl     %ecx, %esi

TL; DR source repolive demo

实施

基本上morton1函数中的每一步都可以通过移动和添加一系列常量来实现:

  1. 0b0101010101010101(替代1和0)
  2. 0b0011001100110011(替代2x 1和0)
  3. 0b0000111100001111(替代4x 1和0)
  4. 0b0000000011111111(替代8x 1和0)
  5. 如果我们使用D维度,我们会使用D-1零和1维度的模式。因此,为了生成这些,它足以生成连续的并按位或应用:

    /// @brief Generates 0b1...1 with @tparam n ones
    template <class T, unsigned n>
    using n_ones = std::integral_constant<T, (~static_cast<T>(0) >> (sizeof(T) * 8 - n))>;
    
    /// @brief Performs `@tparam input | (@tparam input << @tparam width` @tparam repeat times.
    template <class T, T input, unsigned width, unsigned repeat>
    struct lshift_add :
        public lshift_add<T, lshift_add<T, input, width, 1>::value, width, repeat - 1> {
    };
    /// @brief Specialization for 1 repetition, just does the shift-and-add operation.
    template <class T, T input, unsigned width>
    struct lshift_add<T, input, width, 1> : public std::integral_constant<T,
        (input & n_ones<T, width>::value) | (input << (width < sizeof(T) * 8 ? width : 0))> {
    };
    

    现在我们可以在编译时为任意维度生成常量,具有以下内容:

    template <class T, unsigned step, unsigned dimensions = 2u>
    using mask = lshift_add<T, n_ones<T, 1 << step>::value, dimensions * (1 << step), sizeof(T) * 8 / (2 << step)>;
    

    使用相同类型的递归,我们可以为算法x = (x | (x >> K)) & M的每个步骤生成函数:

    template <class T, unsigned step, unsigned dimensions>
    struct deinterleave {
        static T work(T input) {
            input = deinterleave<T, step - 1, dimensions>::work(input);
            return (input | (input >> ((dimensions - 1) * (1 << (step - 1))))) & mask<T, step, dimensions>::value;
        }
    };
    // Omitted specialization for step 0, where there is just a bitwise and
    

    仍然要回答问题&#34;我们需要多少步骤?&#34;。这还取决于尺寸的数量。通常,k步骤计算2^k - 1输出位;每个维度的最大有意义位数由z = sizeof(T) * 8 / dimensions给出,因此只需执行1 + log_2 z步即可。现在问题是我们需要这个constexpr才能将它用作模板参数。我找到解决此问题的最佳方法是通过元编程来定义log2

    template <unsigned arg>
    struct log2 : public std::integral_constant<unsigned, log2<(arg >> 1)>::value + 1> {};
    template <>
    struct log2<1u> : public std::integral_constant<unsigned, 0u> {};
    
    /// @brief Helper constexpr which returns the number of steps needed to fully interleave a type @tparam T.
    template <class T, unsigned dimensions>
    using num_steps = std::integral_constant<unsigned, log2<sizeof(T) * 8 / dimensions>::value + 1>;
    

    最后,我们可以执行一次调用:

    /// @brief Helper function which combines @see deinterleave and @see num_steps into a single call.
    template <class T, unsigned dimensions>
    T deinterleave_first(T n) {
        return deinterleave<T, num_steps<T, dimensions>::value - 1, dimensions>::work(n);
    }