Подтвердить что ты не робот

Как определить, выровнена ли память?

Я новичок в оптимизации кода с инструкциями 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-кода).

Спасибо заранее...

4b9b3361

Ответ 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_tfoo * будет работать, но foo *uintptr_tvoid * и void *uintptr_tfoo * не будет. Вычисление выравнивания также не будет работать надежно, потому что вы проверяете только выравнивание относительно смещения сегмента, которое может быть или не быть тем, что вы хотите.

В заключение: Всегда используйте 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), чтобы определить, установлен ли какой-либо из младших бит?

Ответ 8

Как насчет:

void *mem = malloc(1024+15); 
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );