¿Cómo determinar si la memoria está alineada?


Soy nuevo en la optimización de código con instrucciones SSE/SSE2 y hasta ahora no he llegado muy lejos. Que yo sepa, una función común optimizada para SSE se vería así:

void sse_func(const float* const ptr, int len){
    if( ptr is aligned )
    {
        for( ... ){
            // unroll loop by 4 or 2 elements
        }
        for( ....){
            // handle the rest
            // (non-optimized code)
        }
    } else {
        for( ....){
            // regular C code to handle non-aligned memory
        }
    }
}

Sin embargo, ¿cómo puedo determinar correctamente si la memoria a la que apunta ptr está alineada, por ejemplo, por 16 Bytes? Creo que tengo que incluir la ruta de código C regular para la memoria no alineada, ya que no puedo asegurarme de que cada memoria pasada a esta función esté alineada. Y el uso de los intrínsecos para cargar datos de no alineados la memoria en los registros SSE parece ser horrible lenta (incluso más lenta que el código C normal).

Gracias de antemano...

Author: jww, 2009-12-14

7 answers

EDITAR: lanzar a long es una forma barata de protegerse contra la posibilidad más probable de que int y punteros sean de diferentes tamaños hoy en día.

Como se señala en los comentarios a continuación, hay mejores soluciones si está dispuesto a incluir un encabezado...

Un puntero p está alineado en un límite de 16 bytes iff ((unsigned long)p & 15) == 0.

 24
Author: Pascal Cuoq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-12 17:11:05
#define is_aligned(POINTER, BYTE_COUNT) \
    (((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0)

El cast to void * (o, equivalenty, char *) es necesario porque el estándar solo garantiza una conversión invertible a uintptr_t para void *.

Si desea seguridad de tipo, considere usar una función en línea:

static inline _Bool is_aligned(const void *restrict pointer, size_t byte_count)
{ return (uintptr_t)pointer % byte_count == 0; }

Y esperamos optimizaciones del compilador si byte_count es una constante en tiempo de compilación.

¿por Qué necesitamos para convertir a void * ?

El lenguaje C permite diferentes representaciones para diferentes tipos de puntero, por ejemplo, podría tienen un tipo void * de 64 bits (todo el espacio de direcciones) y un tipo foo * de 32 bits (un segmento).

La conversión foo * -> void * podría implicar un cálculo real, por ejemplo, la adición de un desplazamiento. El estándar también deja a la implementación lo que sucede al convertir punteros (arbitrarios) a enteros, pero sospecho que a menudo se implementa como un noop.

Para una implementación, foo * -> uintptr_t -> foo * iba a funcionar, pero foo * -> uintptr_t -> void * y void * -> uintptr_t -> foo * El cálculo de alineación tampoco funcionaría de manera confiable porque solo verifica la alineación relativa al desplazamiento del segmento, que podría o no ser lo que desea.

En conclusión: Utilice siempre void * para obtener un comportamiento independiente de la implementación.

 42
Author: Christoph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-26 11:24:10

Otras respuestas sugieren una operación AND con bits bajos establecidos, y comparando con cero.

Pero una prueba más directa sería hacer un MOD con el valor de alineación deseado, y comparar con cero.

#define ALIGNMENT_VALUE     16u

if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
    // ptr is aligned
}
 20
Author: Craig McQueen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-13 23:27:43

Con una plantilla de función como

#include <type_traits>

template< typename T >
bool is_aligned(T* p){
    return !(reinterpret_cast<uintptr_t>(p) % std::alignment_of<T>::value);
}

Puede comprobar la alineación en tiempo de ejecución invocando algo como

struct foo_type{ int bar; }foo;
assert(is_aligned(&foo)); // passes

Para comprobar que las alineaciones incorrectas fallan, puede hacer

// would almost certainly fail
assert(is_aligned((foo_type*)(1 + (uintptr_t)(&foo)));
 7
Author: rubicks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-25 17:56:41

¿Puede simplemente 'y' el ptr con 0x03 (alineado en 4s), 0x07 (alineado en 8s) o 0x0f (alineado en 16s) para ver si se establece alguno de los bits más bajos?

 2
Author: Paul Tomblin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-13 23:17:04

Esto es básicamente lo que estoy usando. Al hacer el entero una plantilla, me aseguro de que es tiempo de compilación ampliado, por lo que no voy a terminar con una operación de módulo lento lo que haga.

Siempre me gusta comprobar mi entrada, por lo que la afirmación de tiempo de compilación. Si su valor de alineación es incorrecto, entonces no se compilará...

template <unsigned int alignment>
struct IsAligned
{
    static_assert((alignment & (alignment - 1)) == 0, "Alignment must be a power of 2");

    static inline bool Value(const void * ptr)
    {
        return (((uintptr_t)ptr) & (alignment - 1)) == 0;
    }
};

Para ver lo que está pasando, puedes usar esto:

// 1 of them is aligned...
int* ptr = new int[8];
for (int i = 0; i < 8; ++i)
    std::cout << IsAligned<32>::Value(ptr + i) << std::endl;

// Should give '1'
int* ptr2 = (int*)_aligned_malloc(32, 32);
std::cout << IsAligned<32>::Value(ptr2) << std::endl;
 2
Author: atlaste,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-27 08:03:44

Qué tal:

void *mem = malloc(1024+15); 
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );
 -3
Author: Ramana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-04 09:03:37