quienes - ¿Cómo determinar si la memoria está alineada?(* prueba*para alineación, no alineación)
paises no alineados pdf (7)
¿Puedes simplemente ''y'' ptr con 0x03 (alineado en 4s), 0x07 (alineado en 8s) o 0x0f (alineado en 16s) para ver si se ha configurado alguno de los bits más bajos?
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 ptr
apunta a que está alineada, por ejemplo, con 16 Bytes? Creo que tengo que incluir la ruta de código C normal para la memoria no alineada ya que no puedo asegurarme de que cada memoria que pasa a esta función se alineará. Y el uso de los datos intrínsecos para cargar datos de la memoria no alineada en los registros SSE parece ser muy lento (incluso más lento que el código C normal).
Gracias de antemano...
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);
}
puedes verificar la alineación en el tiempo de ejecución invocando algo como
struct foo_type{ int bar; }foo;
assert(is_aligned(&foo)); // passes
Para verificar que las malas alineaciones fallan, puedes hacer
// would almost certainly fail
assert(is_aligned((foo_type*)(1 + (uintptr_t)(&foo)));
EDITAR: fundir a long
es una forma barata de protegerse contra la posibilidad más probable de que los int y los 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
.
Esto es básicamente lo que estoy usando. Al hacer que el entero sea una plantilla, me aseguro de que haya aumentado el tiempo de compilación, por lo que no terminaré con una operación de módulo lento, haga lo que haga.
Siempre me gusta comprobar mi entrada, por lo tanto, la afirmación del tiempo de compilación. Si su valor de alineación es incorrecto, bueno, entonces no 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;
Otras respuestas sugieren una operación Y 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 compararlo con cero.
#define ALIGNMENT_VALUE 16u
if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
// ptr is aligned
}
Qué tal si:
void *mem = malloc(1024+15);
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );
#define is_aligned(POINTER, BYTE_COUNT) /
(((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0)
El lanzamiento a void *
(o, equivalente, 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é tenemos que convertir a void *
?
El lenguaje C permite diferentes representaciones para diferentes tipos de punteros, por ejemplo, puede tener un tipo void *
64 bits (el espacio completo de direcciones) y un tipo foo *
32 bits (un segmento).
La conversión foo *
-> void *
puede implicar un cálculo real, por ejemplo, agregar un desplazamiento. El estándar también deja en manos de la implementación lo que sucede al convertir punteros (arbitrarios) a enteros, pero sospecho que a menudo se implementa como un noop.
Para tal implementación, foo *
-> uintptr_t
-> foo *
funcionaría, 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 confiable porque solo se verifica la alineación relativa al desplazamiento del segmento, que podría ser o no lo que se desea.
En conclusión: utilice siempre void *
para obtener un comportamiento independiente de la implementación.