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