Как определить, выровнена ли память?
Я новичок в оптимизации кода с инструкциями 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-кода).
Спасибо заранее...
Ответы
Ответ 1
EDIT: литье в long
- это дешевый способ защитить себя от наиболее вероятной возможности того, что int и указатели имеют разные размеры в настоящее время.
Как указано в комментариях ниже, есть лучшие решения, если вы хотите включить заголовок...
Указатель p
выровнен по 16-байтовой границе iff ((unsigned long)p & 15) == 0
.
Ответ 2
#define is_aligned(POINTER, BYTE_COUNT) \
(((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0)
Приведение в void *
(или, эквивалент, char *
) необходимо, поскольку стандарт гарантирует только обратимое преобразование в uintptr_t
для void *
.
Если вам нужна безопасность типов, рассмотрите возможность использования встроенной функции:
static inline _Bool is_aligned(const void *restrict pointer, size_t byte_count)
{ return (uintptr_t)pointer % byte_count == 0; }
и надеемся на оптимизацию компилятора, если byte_count
является константой времени компиляции.
Зачем нам нужно преобразовать в void *
?
Язык C допускает разные представления для разных типов указателей, например, у вас может быть 64-разрядный тип void *
(всего адресного пространства) и 32-разрядный тип foo *
(сегмент).
Преобразование foo *
→ void *
может включать в себя фактическое вычисление, например добавление смещения. Стандарт также оставляет его для реализации, что происходит при преобразовании (произвольных) указателей на целые числа, но я подозреваю, что он часто реализуется как noop.
Для такой реализации foo *
→ uintptr_t
→ foo *
будет работать, но foo *
→ uintptr_t
→ void *
и void *
→ uintptr_t
→ foo *
не будет. Вычисление выравнивания также не будет работать надежно, потому что вы проверяете только выравнивание относительно смещения сегмента, которое может быть или не быть тем, что вы хотите.
В заключение: Всегда используйте void *
, чтобы получить поведение, зависящее от реализации.
Ответ 3
Другие ответы предполагают операцию И с установленными низкими битами и по сравнению с нулем.
Но более прямым испытанием было бы сделать MOD с желаемым значением выравнивания и сравнить с нолем.
#define ALIGNMENT_VALUE 16u
if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
// ptr is aligned
}
Ответ 4
С шаблоном функции, например
#include <type_traits>
template< typename T >
bool is_aligned(T* p){
return !(reinterpret_cast<uintptr_t>(p) % std::alignment_of<T>::value);
}
вы можете проверить выравнивание во время выполнения, вызвав что-то вроде
struct foo_type{ int bar; }foo;
assert(is_aligned(&foo)); // passes
Чтобы проверить, что неудачные выравнивания терпят неудачу, вы можете сделать
// would almost certainly fail
assert(is_aligned((foo_type*)(1 + (uintptr_t)(&foo)));
Ответ 5
Это в основном то, что я использую. Сделав целое число шаблоном, я гарантирую, что он расширит время компиляции, поэтому я не вернусь к медленной работе по модулю, что бы я ни делал.
Мне всегда нравится проверять свой ввод, поэтому, следовательно, утверждение времени компиляции. Если ваше значение выравнивания неверно, тогда он не будет компилироваться...
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;
}
};
Чтобы узнать, что происходит, вы можете использовать это:
// 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;
Ответ 6
Можете ли вы просто "и" ptr с 0x03 (выровнены по 4s), 0x07 (выровнены по 8s) или 0x0f (выровнены по 16s), чтобы определить, установлен ли какой-либо из младших бит?
Ответ 7
Оставьте это профессионалам,
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;
пример:
char D[1];
assert( boost::alignment::is_aligned(&D[0], alignof(double)) ); // might fail, sometimes
Ответ 8
Как насчет:
void *mem = malloc(1024+15);
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );