Я плохо знаком с оптимизацией кода с инструкциями SSE/SSE2, и до сих пор я не стал очень далеким. К моему знанию общая оптимизированная SSE функция была бы похожа на это:
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
}
}
}
Однако, как я правильно определяю если память ptr
точки к выровненные, например, 16 байтов? Я думаю, что должен включать регулярный путь выполнения кода C для неприсоединившейся памяти, поскольку я не могу удостовериться, что каждая память, переданная этой функции, будет выровненная. И использование intrinsics для загрузки данных из невыровненной памяти в регистры SSE, кажется, ужасно медленный (Еще медленнее, чем обычный код C).
Заранее спасибо...
РЕДАКТИРОВАТЬ: приведение к long
- дешевый способ защитить себя от наиболее вероятной возможности того, что int и указатели будут разных размеров в настоящее время.
Как указано в комментариях ниже, есть лучшие решения, если вы хотите включить заголовок ...
Указатель p
выравнивается по 16-байтовой границе iff ((длинное без знака) p & 15) == 0
.
В других ответах предлагается операция И с установленными младшими битами и сравнение с нулем.
Но более простой тест - выполнить MOD с желаемым значением выравнивания. , и сравните с нулем.
#define ALIGNMENT_VALUE 16u
if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
// ptr is aligned
}
#define is_aligned(POINTER, BYTE_COUNT) \
(((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0)
The cast to void *
(or, equivalenty, char *
) is necessary because the standard only guarantees an invertible conversion to uintptr_t
for void *
.
If you want type safety, consider using an inline function:
static inline _Bool is_aligned(const void *restrict pointer, size_t byte_count)
{ return (uintptr_t)pointer % byte_count == 0; }
and hope for compiler optimizations if byte_count
is a compile-time constant.
Why do we need to convert to void *
?
The C language allows different representations for different pointer types, eg you could have a 64-bit void *
type (the whole address space) and a 32-bit foo *
type (a segment).
The conversion foo *
-> void *
might involve an actual computation, eg adding an offset. The standard also leaves it up to the implementation what happens when converting (arbitrary) pointers to integers, but I suspect that it is often implemented as a noop.
For such an implementation, foo *
-> uintptr_t
-> foo *
would work, but foo *
-> uintptr_t
-> void *
and void *
-> uintptr_t
-> foo *
wouldn't. The alignment computation would also not work reliably because you only check alignment relative to the segment offset, which might or might not be what you want.
In conclusion: Always use void *
to get implementation-independant behaviour.
Можете ли вы просто «и» указать ptr с 0x03 (выровнен по 4s), 0x07 (выровнен по 8s) или 0x0f (выровнен по 16s), чтобы увидеть, установлен ли какой-либо из младших битов?