Выровненные аллокации

Чтобы определиться в каком формате вести свой блог, я решил пописать некоторые маленькие «заметки на полях» аля «Tip of the day». Первая заметка посвящена выровненным аллокациям.

Иногда (например, для SSE), нужна память, выровненная по нужной границе (в случае SSE по 16и байтам).

Идём в гугл, смотрим, что нам может дать стандартная библиотека и натыкаемся на следующие функции: _aligned_malloc, _mm_malloc, posix_memalign, memalign. Ну и сюрприз, конечно, что все они нестандартные, компилер и платформ специфик. Перед нами выбор: обложиться дефайнами или написать свой велик, выбор за вами, но я напишу-таки велик.

Алгоритм всего этого дела простой:

  1. выделяем достаточное количество памяти для последующего выравнивания.
  2. выравниваем полученную память на нужную границу.
  3. записываем перед памятью указатель на оригинальную память, дабы потом это корректно удалить.

Собственно код:

#define BM_SYS_ALLOC   ::malloc
#define BM_SYS_DEALLOC ::free

void* bm_alloc_aligned(size_t size, size_t alignment) {  
    // выделяем достаточное количество памяти для последующего выравнивания.
    char* mem = static_cast<char*>(BM_SYS_ALLOC(size + alignment + sizeof(size_t)));
    if ( mem ) {
        // запоминаем указатель на оригинальную память
        const char* const orig_mem = mem;
        // сдвигаем на размер указателя,
        // дабы потом выровнять и осталось место под оригинальный указатель
        mem += sizeof(size_t);
        // вычисляем оффсет для выравнивания на нужную границу
        const size_t offset = alignment - (reinterpret_cast<size_t>(mem) % alignment);
        // сдвигаем и получаем выровненную память
        mem += offset;
        // записываем перед выровненной памятью наш оригинальный указатель
        *reinterpret_cast<size_t*>(mem - sizeof(size_t)) = reinterpret_cast<size_t>(orig_mem);
    }
    return mem;
}

void bm_dealloc_aligned(void* ptr) {  
    if ( ptr ) {
        // сдвигаемся на кусок памяти, где записан оригинальный указатель на память
        char* mem = static_cast<char*>(ptr) - sizeof(size_t);
        // получаем его
        mem = reinterpret_cast<char*>(*reinterpret_cast<size_t*>(mem));
        // удаляем как обычную память
        BM_SYS_DEALLOC(mem);
    }
}