Лучший кроссплатформенный метод для выравнивания памяти

Вот код, который я обычно использую для выравнивания памяти с помощью Visual Studio и GCC

inline void* aligned_malloc(size_t size, size_t align) {
    void *result;
    #ifdef _MSC_VER 
    result = _aligned_malloc(size, align);
    #else 
     if(posix_memalign(&result, align, size)) result = 0;
    #endif
    return result;
}

inline void aligned_free(void *ptr) {
    #ifdef _MSC_VER 
        _aligned_free(ptr);
    #else 
      free(ptr);
    #endif

}

Этот код в целом хорошо? Я также видел, как люди используют _mm_malloc, _mm_free, В большинстве случаев я хочу выровнять память, это использовать SSE/AVX. Могу ли я использовать эти функции в целом? Это сделало бы мой код намного проще.

Наконец, легко создать мою собственную функцию для выравнивания памяти (см. Ниже). Почему тогда существует так много различных общих функций для выравнивания памяти (многие из которых работают только на одной платформе)?

Этот код выполняет 16-байтовое выравнивание.

float* array = (float*)malloc(SIZE*sizeof(float)+15);

// find the aligned position
// and use this pointer to read or write data into array
float* alignedArray = (float*)(((unsigned long)array + 15) & (~0x0F));

// dellocate memory original "array", NOT alignedArray
free(array);
array = alignedArray = 0;

См.: http://www.songho.ca/misc/alignment/dataalign.html и Как выделить выровненную память только с использованием стандартной библиотеки?

Редактировать: В случае, если кому-то все равно, я получил идею для моей функции align_malloc() из Eigen (Eigen/src/Core/util/Memory.h)

Изменить: я только что обнаружил, что posix_memalign не определено для MinGW. Тем не мение, _mm_malloc работает для Visual Studio 2012, GCC, MinGW и компилятора Intel C++, поэтому, похоже, это наиболее удобное решение в целом. Это также требует использования собственного _mm_free функция, хотя в некоторых реализациях вы можете передавать указатели из _mm_malloc к стандарту free / delete,

5 ответов

Решение

Первая функция, которую вы предлагаете, действительно будет работать нормально.

Ваша функция "homebrew" также работает, но имеет недостаток, заключающийся в том, что если значение уже выровнено, вы просто потратили 15 байтов. Иногда это может не иметь значения, но ОС вполне может обеспечить правильное распределение памяти без каких-либо потерь (и если ее нужно выровнять по 256 или 4096 байтам, вы рискуете потратить много памяти, добавив "alignment-1") байт).

Пока вы в порядке вызова специальной функции для освобождения, ваш подход в порядке. Я бы сделал твой #ifdefИ наоборот: начните с опций, определенных стандартами, и перейдите к платформо-зависимым. Например

  1. Если __STDC_VERSION__ >= 201112L использование aligned_alloc,
  2. Если _POSIX_VERSION >= 200112L использование posix_memalign,
  3. Если _MSC_VER определяется, используйте материал Windows.
  4. ...
  5. Если ничего не помогает, просто используйте malloc/free и отключите код SSE/AVX.

Проблема сложнее, если вы хотите иметь возможность передать выделенный указатель на free; это действует на всех стандартных интерфейсах, но не на Windows и не обязательно с устаревшими memalign функционируют некоторые unix-подобные системы.

Вот исправление примера user2093113, прямой код для меня не создавался (void* неизвестный размер). Я также поместил это в оператор переопределения класса шаблона new/delete, чтобы вам не приходилось делать выделение и размещение вызова новым.

#include <memory>

template<std::size_t Alignment>
class Aligned
{
public:
    void* operator new(std::size_t size)
    {
        std::size_t space = size + (Alignment - 1);
        void *ptr = malloc(space + sizeof(void*));
        void *original_ptr = ptr;

        char *ptr_bytes = static_cast<char*>(ptr);
        ptr_bytes += sizeof(void*);
        ptr = static_cast<void*>(ptr_bytes);

        ptr = std::align(Alignment, size, ptr, space);

        ptr_bytes = static_cast<char*>(ptr);
        ptr_bytes -= sizeof(void*);
        std::memcpy(ptr_bytes, &original_ptr, sizeof(void*));

        return ptr;
    }

    void operator delete(void* ptr)
    {
        char *ptr_bytes = static_cast<char*>(ptr);
        ptr_bytes -= sizeof(void*);

        void *original_ptr;
        std::memcpy(&original_ptr, ptr_bytes, sizeof(void*));

        std::free(original_ptr);
    }
};

Используйте это так:

class Camera : public Aligned<16>
{
};

Еще не тестировал кросс-платформенность этого кода.

Если ваш компилятор поддерживает это, C++11 добавляет std::align функция для выравнивания указателя во время выполнения. Вы можете реализовать свой собственный malloc/free следующим образом (не проверено):

template<std::size_t Align>
void *aligned_malloc(std::size_t size)
{
    std::size_t space = size + (Align - 1);
    void *ptr = malloc(space + sizeof(void*));
    void *original_ptr = ptr;

    char *ptr_bytes = static_cast<char*>(ptr);
    ptr_bytes += sizeof(void*);
    ptr = static_cast<void*>(ptr_bytes);

    ptr = std::align(Align, size, ptr, space);

    ptr_bytes = static_cast<void*>(ptr);
    ptr_bytes -= sizeof(void*);
    std::memcpy(ptr_bytes, original_ptr, sizeof(void*));

    return ptr;
}

void aligned_free(void* ptr)
{
    void *ptr_bytes = static_cast<void*>(ptr);
    ptr_bytes -= sizeof(void*);

    void *original_ptr;
    std::memcpy(&original_ptr, ptr_bytes, sizeof(void*));

    std::free(original_ptr);
}

Тогда вам не нужно хранить исходное значение указателя, чтобы освободить его. Является ли это 100% портативным, я не уверен, но я надеюсь, что кто-то исправит меня, если нет!

Вот мои 2 цента:

temp = new unsigned char*[num];
AlignedBuffers = new unsigned char*[num];
for (int i = 0; i<num; i++)
{
    temp[i] = new  unsigned char[bufferSize +15];
    AlignedBuffers[i] = reinterpret_cast<unsigned char*>((reinterpret_cast<size_t>
                        (temp[i% num]) + 15) & ~15);// 16 bit alignment in preperation for SSE
}
Другие вопросы по тегам