如果您可以使用特定于体系结构的指令,那么您可能能够加速操作,超出使用 bit-twiddeling hack 所能达到的速度:
例如,如果您为 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。
这里的技巧是,一个数字与其自身的无进位乘法将返回一个位模式,该模式包含参数的原始位以及交错的零位。所以它与上面显示的 _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 函数的任何技巧,我努力了几个月。