代码之家  ›  专栏  ›  技术社区  ›  Pavel P

在arm/neon[duplicate]中有效地从整数中分离偶数和奇数位

  •  0
  • Pavel P  · 技术社区  · 7 年前

    如何对给定[x,y]的morton码(z阶)进行编码/解码,生成64位morton码,反之亦然?

    0 回复  |  直到 8 年前
        1
  •  11
  •   Pavel P    7 年前

    如果您可以使用特定于体系结构的指令,那么您很可能能够加快操作速度,而不仅仅是使用位抖动黑客:

    例如,如果您为Intel Haswell及更高版本的CPU编写代码,则可以使用包含 pext pdep 说明。这些可以用来构建你的函数。

    #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);
    }
    
    void morton_to_xy(uint64_t m, uint32_t *x, uint32_t *y)
    {
      *x = _pext_u64(m, 0x5555555555555555);
      *y = _pext_u64(m, 0xaaaaaaaaaaaaaaaa);
    }
    

    现在很多CPU都支持无进位乘法。在手臂上那会是 vmul_p8 PCLMULQDQ 来自CLMUL指令集(自2010年起提供)。

    这里的诀窍是,一个数与其自身的无进位乘法将返回一个位模式,该模式包含参数的原始位,且交错为零位。因此它与上面显示的_pdep_u32(x,0x55555555)相同。E、 g.它将转换以下字节:

     +----+----+----+----+----+----+----+----+
     | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
     +----+----+----+----+----+----+----+----+
    

    进入:

     +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
     | 0  | b7 | 0  | b6 | 0  | b5 | 0  | b4 | 0  | b3 | 0  | b2 | 0  | b1 | 0  | b0 |
     +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
    

    现在,您可以构建xy_to_morton函数,如下所示(CLMUL指令集如下所示):

    #include <wmmintrin.h>
    #include <stdint.h>
    
    // on GCC, compile with option -mpclmul
    
    uint64_t carryless_square (uint32_t x)
    {
      uint64_t val[2] = {x, 0};
      __m128i *a = (__m128i * )val;
      *a = _mm_clmulepi64_si128 (*a,*a,0);
      return val[0];
    }
    
    uint64_t xy_to_morton (uint32_t x, uint32_t y)
    {
      return carryless_square(x)|(carryless_square(y) <<1);
    }
    

    _mm_clmulepi64_si128 生成一个128位的结果,我们只使用较低的64位。因此,您甚至可以改进上面的版本,并使用单个的\u mm_clmulepi64_si128来完成这项工作。

    这是你在主流平台上所能达到的最高水平(例如,带有NEON和x86的现代ARM)。不幸的是,我不知道使用密码指令来加速morton_ux y函数的任何诀窍,我努力了几个月。

        2
  •  8
  •   Johan    7 年前
    void xy2d_morton(uint64_t x, uint64_t y, uint64_t *d)
    {
        x = (x | (x << 16)) & 0x0000FFFF0000FFFF;
        x = (x | (x << 8)) & 0x00FF00FF00FF00FF;
        x = (x | (x << 4)) & 0x0F0F0F0F0F0F0F0F;
        x = (x | (x << 2)) & 0x3333333333333333;
        x = (x | (x << 1)) & 0x5555555555555555;
    
        y = (y | (y << 16)) & 0x0000FFFF0000FFFF;
        y = (y | (y << 8)) & 0x00FF00FF00FF00FF;
        y = (y | (y << 4)) & 0x0F0F0F0F0F0F0F0F;
        y = (y | (y << 2)) & 0x3333333333333333;
        y = (y | (y << 1)) & 0x5555555555555555;
    
        *d = x | (y << 1);
    }
    
    // morton_1 - extract even bits
    
    uint32_t morton_1(uint64_t x)
    {
        x = x & 0x5555555555555555;
        x = (x | (x >> 1))  & 0x3333333333333333;
        x = (x | (x >> 2))  & 0x0F0F0F0F0F0F0F0F;
        x = (x | (x >> 4))  & 0x00FF00FF00FF00FF;
        x = (x | (x >> 8))  & 0x0000FFFF0000FFFF;
        x = (x | (x >> 16)) & 0x00000000FFFFFFFF;
        return (uint32_t)x;
    }
    
    void d2xy_morton(uint64_t d, uint64_t &x, uint64_t &y)
    {
        x = morton_1(d);
        y = morton_1(d >> 1);
    }
    
        3
  •  4
  •   Sami Kuhmonen    11 年前

    不管比特数是多少,中间代码都是一样的。如果你不需要这么快的话

    uint32_t x;
    uint32_t y;
    uint64_t z = 0;
    
    for (int i = 0; i < sizeof(x) * 8; i++)
    {
      z |= (x & (uint64_t)1 << i) << i | (y & (uint64_t)1 << i) << (i + 1);
    }
    

    uint64_t x;
    uint64_t y;
    uint64_t z = 0;
    
    x = (x | (x << 16)) & 0x0000FFFF0000FFFF;
    x = (x | (x << 8)) & 0x00FF00FF00FF00FF;
    x = (x | (x << 4)) & 0x0F0F0F0F0F0F0F0F;
    x = (x | (x << 2)) & 0x3333333333333333;
    x = (x | (x << 1)) & 0x5555555555555555;
    
    y = (y | (y << 16)) & 0x0000FFFF0000FFFF;
    y = (y | (y << 8)) & 0x00FF00FF00FF00FF;
    y = (y | (y << 4)) & 0x0F0F0F0F0F0F0F0F;
    y = (y | (y << 2)) & 0x3333333333333333;
    y = (y | (y << 1)) & 0x5555555555555555;
    
    z = x | (y << 1);