Как определить, выровнена ли память?
-
19-09-2019 - |
Вопрос
Я новичок в оптимизации кода с помощью инструкций 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 для выровненной памяти, поскольку я не могу быть уверен, что каждая память, переданная этой функции, будет выровнена.И использование встроенных функций для загрузки данных из невыровненной памяти в регистры SSE кажется ужасно медленным (даже медленнее, чем обычный код C).
Заранее благодарю вас...
Решение
Редактировать:кастинг для long
это дешевый способ защитить себя от наиболее вероятной возможности того, что в настоящее время int и указатели имеют разные размеры.
Как указано в комментариях ниже, есть лучшие решения, если вы готовы включить заголовок...
Указатель p
выравнивается по 16-байтовой границе iff ((unsigned long)p & 15) == 0
.
Другие советы
#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 *
чтобы получить поведение, не зависящее от реализации.
Другие ответы предполагают операцию AND с установленными младшими битами и сравнение с нулем.
Но более простым тестом было бы создать мод с желаемым значением выравнивания и сравнить с нулем.
#define ALIGNMENT_VALUE 16u
if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
// ptr is aligned
}
С помощью шаблона функции, подобного
#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)));
Это в основном то, что я использую.Делая целое число шаблоном, я гарантирую, что это увеличит время компиляции, поэтому я не буду в конечном итоге выполнять медленную операцию по модулю, что бы я ни делал.
Мне всегда нравится проверять свои входные данные, отсюда и утверждение о времени компиляции.Если ваше значение выравнивания неверно, что ж, тогда оно не будет компилироваться...
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;
Можете ли вы просто 'и' ptr с 0x03 (выровнен по 4s), 0x07 (выровнен по 8s) или 0x0f (выровнен по 16s), чтобы увидеть, установлен ли какой-либо из младших битов?
Предоставьте это профессионалам,
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
Как насчет:
void *mem = malloc(1024+15);
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );