二维莫顿码encode/decode 64bits
2D morton code encode/decode 64bits
如何 encode/decode 给定 [x, y] 作为 32 位无符号整数生成 64 位莫顿代码,反之亦然?
我确实有 xy2d 和 d2xy,但仅适用于产生 32 位莫顿数的 16 位宽坐标。网上找了很多,都没找到。请帮忙。
不管位数如何,原始代码都是一样的。如果你不需要超快的 bit twiddling 版本,这就可以
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);
}
如果您需要更快的位操作,那么这个应该可以。请注意,x 和 y 必须是 64 位变量。
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);
如果您可以使用特定于体系结构的指令,您将可能能够加速操作,超出使用位旋转 hacks 的可能范围:
例如,如果您为 Intel Haswell 和更高版本的 CPU 编写代码,您可以使用包含 pext
和 pdep
指令的 BMI2 指令集。这些(以及其他很棒的东西)可用于构建您的功能。
这是一个完整的示例(使用 GCC 测试):
#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 或 ARM 平台,则并非全部丢失。您仍然可以至少从特定于密码学的说明中获得有关 xy_to_morton 函数的帮助。
现在很多 CPU 都支持无进位乘法。在 ARM 上,这将是来自 NEON 指令集的 vmul_p8
。在 X86 上,您会发现它来自 CLMUL 指令集(自 2010 年起可用)PCLMULQDQ
。
这里的诀窍是,一个数字与其自身的无进位乘法将 return 一个位模式,其中包含零位交错的参数的原始位。所以它与上面显示的 _pdep_u32(x,0x55555555) 相同。例如。它变成以下字节:
+----+----+----+----+----+----+----+----+
| 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 位。所以你甚至可以改进上面的版本并使用一个 _mm_clmulepi64_si128 来完成这项工作。
这与您在主流平台(例如带有 NEON 和 x86 的现代 ARM)上所能获得的一样好。不幸的是,我不知道有什么技巧可以使用加密指令来加速 morton_to_xy 函数,我真的很努力地尝试了几个月。
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);
}
如何 encode/decode 给定 [x, y] 作为 32 位无符号整数生成 64 位莫顿代码,反之亦然? 我确实有 xy2d 和 d2xy,但仅适用于产生 32 位莫顿数的 16 位宽坐标。网上找了很多,都没找到。请帮忙。
不管位数如何,原始代码都是一样的。如果你不需要超快的 bit twiddling 版本,这就可以
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);
}
如果您需要更快的位操作,那么这个应该可以。请注意,x 和 y 必须是 64 位变量。
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);
如果您可以使用特定于体系结构的指令,您将可能能够加速操作,超出使用位旋转 hacks 的可能范围:
例如,如果您为 Intel Haswell 和更高版本的 CPU 编写代码,您可以使用包含 pext
和 pdep
指令的 BMI2 指令集。这些(以及其他很棒的东西)可用于构建您的功能。
这是一个完整的示例(使用 GCC 测试):
#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 或 ARM 平台,则并非全部丢失。您仍然可以至少从特定于密码学的说明中获得有关 xy_to_morton 函数的帮助。
现在很多 CPU 都支持无进位乘法。在 ARM 上,这将是来自 NEON 指令集的 vmul_p8
。在 X86 上,您会发现它来自 CLMUL 指令集(自 2010 年起可用)PCLMULQDQ
。
这里的诀窍是,一个数字与其自身的无进位乘法将 return 一个位模式,其中包含零位交错的参数的原始位。所以它与上面显示的 _pdep_u32(x,0x55555555) 相同。例如。它变成以下字节:
+----+----+----+----+----+----+----+----+
| 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 位。所以你甚至可以改进上面的版本并使用一个 _mm_clmulepi64_si128 来完成这项工作。
这与您在主流平台(例如带有 NEON 和 x86 的现代 ARM)上所能获得的一样好。不幸的是,我不知道有什么技巧可以使用加密指令来加速 morton_to_xy 函数,我真的很努力地尝试了几个月。
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);
}