Pregunta

Esta pregunta: Cómo desentrelazar bits (¿DesMortonizar?) tiene una buena respuesta para extraer una de las dos mitades de un número de Morton (solo los bits impares), pero necesito una solución que extraiga ambas partes (los bits impares y los pares) en el menor número de operaciones posible.

Para mi uso, necesitaría tomar un int de 32 bits y extraer dos int de 16 bits, donde uno son los bits pares y el otro son los bits impares desplazados a la derecha 1 bit, por ejemplo.

input,  z: 11101101 01010111 11011011 01101110

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

Parece haber muchas soluciones que utilizan turnos y máscaras con números mágicos para generar números de Morton (es decir,bits entrelazados), p.e. Intercalar bits mediante números mágicos binarios, pero todavía no he encontrado nada para hacer lo contrario (es decir,desintercalado).

ACTUALIZAR

Después de volver a leer la sección de Hacker's Delight sobre mezclas/desordenes perfectas, encontré algunos ejemplos útiles que adapté de la siguiente manera:

// 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);
}

Creo que esto aún se puede mejorar, tanto en su forma escalar actual como aprovechando SIMD, por lo que todavía estoy interesado en mejores soluciones (ya sean escalares o SIMD).

¿Fue útil?

Solución

Si su procesador maneja un INTS de 64 bits de manera eficiente, podría combinar las operaciones ...

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

Otros consejos

Código para Intel Haswell y luego CPUs.Puede usar el conjunto de instrucciones BMI2 que contiene las instrucciones de PEXT y PDEP.Estos pueden (entre otras grandes cosas) ser utilizadas para construir sus funciones.

#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);
}

En caso de que alguien esté usando códigos de Morton en 3D, por lo que necesita leer un bit cada 3 y 64 bits aquí es la función que utilizo:

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)

Si necesita velocidad de lo que puede usar la búsqueda de la tabla para una conversión de bytes a la vez (la tabla de dos bytes es más rápida pero a la grande).El procedimiento está hecho bajo Delphi IDE, pero el ensamblador / algorithem es el mismo.

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;

No quería limitarme a un número entero de tamaño fijo y hacer listas de comandos similares con constantes codificadas, así que desarrollé una solución C++11 que utiliza metaprogramación de plantillas para generar las funciones y las constantes.El código ensamblador generado con -O3 Parece lo más ajustado posible sin usar el IMC:

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 repositorio de origen y demo en vivo.


Implementación

Básicamente cada paso en el morton1 La función funciona cambiando y agregando a una secuencia de constantes que se ven así:

  1. 0b0101010101010101 (alternar 1 y 0)
  2. 0b0011001100110011 (alternar 2x 1 y 0)
  3. 0b0000111100001111 (alternar 4x 1 y 0)
  4. 0b0000000011111111 (alternar 8x 1 y 0)

Si tuviéramos que usar D dimensiones, tendríamos un patrón con D-1 ceros y 1 uno.Entonces para generarlos basta con generar unos consecutivos y aplicar algunos bit a bit o:

/// @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))> {
};

Ahora que podemos generar las constantes en tiempo de compilación para dimensiones arbitrarias con lo siguiente:

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)>;

Con el mismo tipo de recursividad podemos generar funciones para cada uno de los pasos del algoritmo. 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

Queda por responder a la pregunta "¿cuántos pasos necesitamos?".Esto también depende del número de dimensiones.En general, k pasos computar 2^k - 1 bits de salida;el número máximo de bits significativos para cada dimensión viene dado por z = sizeof(T) * 8 / dimensions, por lo tanto basta con tomar 1 + log_2 z pasos.El problema ahora es que necesitamos esto como constexpr para usarlo como parámetro de plantilla.La mejor manera que encontré para solucionar esto es definir log2 a través de metaprogramación:

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>;

Y por último, podemos realizar una única llamada:

/// @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);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top