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

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

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

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

Ответы

Ответ 1

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

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

Ответ 2

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

  • Если __STDC_VERSION__ >= 201112L использовать aligned_alloc.
  • Если _POSIX_VERSION >= 200112L использовать posix_memalign.
  • Если _MSC_VER определен, используйте материалы Windows.
  • ...
  • Если все остальное не работает, просто используйте malloc/free и отключите код SSE/AVX.

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

Ответ 3

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

#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>
{
};

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

Ответ 4

Если ваш компилятор поддерживает его, С++ 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% портативным, я не уверен, но я надеюсь, что кто-то исправит меня, если нет!

Ответ 5

Вот мои 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
}