Pregunta

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

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 determinar correctamente si los puntos ptr memoria para se alinea por ejemplo 16 Bytes? Creo que tengo para incluir la ruta de código C regular para la memoria no alineados ya que no puede asegurarse de que cada recuerdo pasado a esta función se alineará. Y el uso de los intrínsecos para cargar datos de la memoria no alineada en los registros de la ESS parece ser horrible lenta (incluso más lento que el código normal C).

Gracias de antemano ...

¿Fue útil?

Solución

EDIT: fundición a long es una forma barata de protegerse contra la posibilidad más probable de int y punteros que son diferentes tamaños en la actualidad.

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

A p puntero está alineado en un 16-byte ((unsigned long)p & 15) == 0 iff límite.

Otros consejos

#define is_aligned(POINTER, BYTE_COUNT) \
    (((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0)

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

Si quieres seguridad de tipos, considerar el uso de 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 esperanza para las 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, que podría tener un tipo de 64 bits void * (todo el espacio de direcciones) y un tipo foo * 32 bits (un segmento).

El foo * conversión -> void * podría implicar un cálculo real, por ejemplo la adición de un desplazamiento. La norma también deja en manos de la ejecución lo que sucede cuando la conversión de punteros (arbitrarias) a números enteros, pero sospecho que se implementa a menudo como un NOOP.

En tal implementación, foo * -> uintptr_t -> foo * quiere trabajar, pero foo * -> uintptr_t -> void * y void * -> uintptr_t -> foo * no lo haría. El cálculo de alineación tampoco funcionaría de manera fiable, ya que sólo comprueba la alineación con relación al segmento de compensación, lo que podría o no podría ser lo que quieres.

En conclusión:. Utilice siempre void * para conseguir un comportamiento aplicación-independiente

Otras respuestas sugieren una operación AND con los bits de ajuste bajo, y comparando a cero.

Sin embargo, un examen más recta hacia adelante sería hacer un MOD con el valor de alineación deseada, y compara a cero.

#define ALIGNMENT_VALUE     16u

if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
    // ptr is aligned
}

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

se puede comprobar la alineación en tiempo de ejecución mediante la invocación de algo así como

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

Para comprobar que los malos alineamientos fallan, se puede hacer

// would almost certainly fail
assert(is_aligned((foo_type*)(1 + (uintptr_t)(&foo)));

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

Siempre me gusta comprobar mi entrada, por lo que por lo tanto, la afirmación de tiempo de compilación. Si el valor de alineación está mal pues entonces no va a 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, puede utilizar lo siguiente:

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

Puede usted acaba 'y' El PTR con 0x03 (alineado en 4s), 0x07 (alineado en 8s) o 0x0F (alineado en 16s) para ver si se ha armado cualquiera de los bits más bajos?

Deja que a los profesionales,

https : //www.boost.org/doc/libs/1_65_1/doc/html/align/reference.html#align.reference.functions.is_aligned

bool is_aligned(const void* ptr, std::size_t alignment) noexcept; 

ejemplo:

        char D[1];
        assert( boost::alignment::is_aligned(&D[0], alignof(double)) ); //  might fail, sometimes

¿Qué hay de:

void *mem = malloc(1024+15); 
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top