Вопрос

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

Предоставьте это профессионалам,

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

Как насчет:

void *mem = malloc(1024+15); 
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top