Как сделать 24-байтовое распределение размера, когда минимальное выравнивание распределения составляет 16 байтов? - PullRequest
1 голос
/ 07 апреля 2020

Мое приложение выполняет много выделений ровно 24 байта, но я использую стороннюю библиотеку, которая требует, чтобы распределитель предоставил как минимум 16-байтовое выравнивание.

Итак, если я скомпилирую jemalloc настроен на 8-байтовое выравнивание (--with-lg-quantum=3), я получаю 24-байтовое распределение, но моя сторонняя библиотека не работает.

Если я компилирую jemalloc, настроенный на 16-байтовое выравнивание (--with-lg-quantum=4) мои malloc((size_t)24) вызовы выделяют 32 байта. Это на 33,3% больше использования памяти. Тем не менее, мне нужны регулярные вызовы malloc((size_t)24) для выделения 16-байтового выравнивания (следовательно, 32 байта), чтобы моя сторонняя библиотека работала.

Как я могу выделить из моего приложения 24-байтовые блоки (8-байтовое выравнивание) ) эффективно использовать память?

Я пытался aligned_alloc(8, 24), но он все равно выделяет 32 байта, выровненные по 16 байтов.

Ответы [ 2 ]

8 голосов
/ 07 апреля 2020

Если вы делаете много выделений ровно из 24 байтов, и эффективность использования памяти этих выделений является проблемой, вам не следует использовать malloc напрямую вообще . Вы должны использовать распределитель пула размером 24 байта.

Распределители пула выделяют большой кусок памяти из кучи, а затем разделяют его на блоки фиксированного размера указанного вами размера. Таким образом, вы не только избегаете накладных расходов на выравнивание, но также избегаете накладных расходов на информацию, используемую кучей для отслеживания свободных блоков данных. Вы также помогаете избежать фрагментации, вызванной такими крошечными выделениями. Освободить эти распределения довольно быстро (как делает их). И т. Д.

С помощью распределителя пулов вы должны иметь возможность распределять именно то, что вам нужно, не мешая работе библиотеки. Вы можете выделить кусок памяти для 10 000 24-байтовых блоков, и единственными дополнительными затратами будут учетные записи, необходимые для отслеживания свободных блоков (которые в большинстве случаев могут использовать свободные блоки сами , если вы умны) .

2 голосов
/ 07 апреля 2020

Ваше приложение выделяет много 24-байтовых объектов, и вы хотите объединить эти цели:

  • выровнять каждый 24-байтовый объект по 16-байтовой границе, предположительно, чтобы прочитать его содержимое с помощью SIMD инструкции
  • используют как можно меньше памяти, в идеале всего 24 байта на объект.

Эти цели несовместимы: если объекты требуют выравнивания по 16 байтов, они должны быть как минимум На расстоянии 32 байта независимо от того, как вы распределяете память.

Библиотека C malloc(), вероятно, уже обеспечивает 16-байтовое выравнивание в вашей системе (это обычное требование в 64-битных системах для SIMD-совместимых данных ), но может использовать 8-байтовый резерв в конце блока для своих собственных данных учета. jemalloc() конечно, делает. Таким образом, издержки не тратятся впустую, а свойственны алгоритму распределения.

Распределение объектов в пулах не помогает при упаковке из-за ограничения выравнивания. Это может быть более эффективным, но современные реализации malloc() являются удивительно эффективными, и некоторые действительно используют пулы на основе потоков (например, tcmalloc()).

Разработка собственной схемы размещения сложна и подвержена ошибкам, связывая Пользовательская реализация malloc() не тривиальна, так как может вызвать проблемы с собственным использованием C библиотечными функциями malloc(). Я бы настоятельно рекомендовал не использовать эти подходы, если вы не очень хорошо разбираетесь в C и хорошо разбираетесь в своей системе.

Существует одно возможное направление для улучшения упаковки: если вы также выделяете много 8-байтовых объектов, Вы можете чередовать их в комбинированных пулах по 32 байта, используя первые 24 байта для 24-байтового объекта, выровненного по 16-байтовой границе, и 8 оставшихся байтов для отдельного 8-байтового объекта, выровненного по 8-байтовой границе. .

Другой подход - разделить хранилище ваших 24-байтовых объектов на массив 16-байтовых частей и другой массив 8-байтовых частей, используя тот же индекс для доступа к частям одного и того же логического объекта. , Если вы знаете максимальное количество таких объектов для размещения, это работоспособное решение. Для доступа к частям вы должны использовать индексные значения вместо указателей. Это может потребовать существенных модификаций вашего кода.

Память довольно дешева и доступна в современных системах. Если вы не нацеливаетесь на существующие развернутые встроенные системы, указание большего объема ОЗУ для вашего приложения является простым и эффективным подходом.

Вот распределитель пулов для 24-байтовых объектов с очень маленькими издержками. Попробуйте и посмотрите, используете ли вы меньше памяти и получите лучшую производительность:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

typedef struct pool_link_t {
    struct pool_link_t *next;   // generic link for the free list
} pool_link_t;

typedef struct pool_page_t {
    struct pool_block_t *head;   // pointer to the block at the start of each page
} pool_page_t;

typedef struct pool_block_t pool_block_t;
struct pool_block_t {
    pool_block_t *head;         // at the start of each page
    pool_block_t *next, *prev;  // pool_block linkage
    size_t block_size;          // mmapped size
    size_t avail_page_count;    // number of unused pages
    size_t avail_count;         // number of unused objects
    size_t free_count;          // length of free list
    pool_link_t *free_list;     // free list
    pool_link_t *avail_ptr;     // pointer to unused object area
};

#define PAGE_SIZE        0x1000     // system dependent
#define POOL_BLOCK_SIZE  0x100000   // must be a multiple of PAGE_SIZE
#define POOL_OBJ_SIZE    24         // must be a multiple of sizeof(void*)

static pool_block_t dummy_arena = {
    &dummy_arena, &dummy_arena, &dummy_arena, 0, 0, 0, 0, NULL, NULL,
};
static pool_block_t *pool24 = &dummy_arena;

void *malloc24(void) {
    pool_block_t *p, *startp;
    for (startp = p = pool24;; pool24 = p = p->next) {
        if (p->free_count) {
            pool_link_t *link = p->free_list;
            p->free_list = link->next;
            p->free_count--;
            return link;
        }
        if (p->avail_count) {
            void *ptr = p->avail_ptr;
            p->avail_ptr += POOL_OBJ_SIZE / sizeof(pool_block_t*);
            if (--p->avail_count == 0) {
                if (p->avail_page_count) {  // prep the next page of the block
                    pool_page_t *page = (void *)((unsigned char *)p + POOL_BLOCK_SIZE - p->avail_page_count * PAGE_SIZE);
                    page->head = p;
                    p->avail_ptr = (void *)(page + 1);
                    p->avail_count = (PAGE_SIZE - sizeof(pool_block_t*)) / POOL_OBJ_SIZE;
                    p->avail_page_count--;
                }
            }
            return ptr;
        }
        if (p->next == startp) {
            pool_block_t *np = mmap(NULL, POOL_BLOCK_SIZE,
                                    PROT_READ | PROT_WRITE,
                                    MAP_ANON | MAP_PRIVATE, -1, 0);
            if (np == MAP_FAILED)
                return NULL;
            np->head = np;
            np->block_size = POOL_BLOCK_SIZE;
            // prep the first page of the block
            np->avail_page_count = POOL_BLOCK_SIZE / PAGE_SIZE - 1;
            np->avail_count = (PAGE_SIZE - sizeof(pool_block_t)) / POOL_OBJ_SIZE;
            np->avail_ptr = (void *)(np + 1);
            np->free_count = 0;
            np->free_list = NULL;
            // link the block in the arena
            np->prev = p;
            np->next = p->next;
            p->next = np->next->prev = np;
        }
    }
}

void free24(void *p) {
    pool_link_t *lp;
    if ((lp = p) != NULL) {
        pool_block_t *np = (void *)((uintptr_t)p & ~(PAGE_SIZE - 1));
        np = np->head;
        lp->next = np->free_list;
        np->free_list = lp;
        np->free_count++;
    }
}

void trim_arena24(void) {
    pool_block_t *p;
    pool24 = &dummy_arena;
    while ((p = dummy_arena.next) != &dummy_arena) {
        if (p->free_count == (PAGE_SIZE - sizeof(pool_block_t)) / POOL_OBJ_SIZE +
            (PAGE_SIZE - sizeof(pool_block_t*)) / POOL_OBJ_SIZE * (POOL_BLOCK_SIZE / PAGE_SIZE - 1 - p->avail_page_count)) {
            dummy_arena.next = p->next;
            p->next->prev = p->prev;
            munmap(p, p->block_size);
        }
    }
}

void free_arena24(void) {
    pool_block_t *p;
    pool24 = &dummy_arena;
    while ((p = dummy_arena.next) != &dummy_arena) {
        dummy_arena.next = p->next;
        p->next->prev = p->prev;
        munmap(p, p->block_size);
    }
}

#define TRACE(s)  //s
#define TEST_COUNT (16 << 20)
static void *ptr[TEST_COUNT];

#ifdef BENCH_REF
#define malloc24()  malloc(24)
#define free24(p)   free(p)
#endif

int main(void) {
    int i;

    TRACE(printf("testing %d\n", TEST_COUNT));
    for (i = 0; i < TEST_COUNT; i++) {
        ptr[i] = malloc24();
        TRACE(printf("%d: malloc24() -> %p\n", i, ptr[i]));
    }
    for (i = 0; i < TEST_COUNT; i++) {
        int n = rand() % TEST_COUNT;
        if (ptr[n]) {
            TRACE(printf("%d: free24(%p)\n", n, ptr[n]));
            free24(ptr[n]);
            ptr[n] = NULL;
        }
    }
    for (i = 0; i < TEST_COUNT; i++) {
        if (!ptr[i]) {
            ptr[i] = malloc24();
            TRACE(printf("%d: malloc24() -> %p\n", i, ptr[i]));
        }
    }
    for (i = 0; i < TEST_COUNT; i++) {
        TRACE(printf("%d: free24(%p)\n", i, ptr[i]));
        free24(ptr[i]);
        ptr[i] = NULL;
    }
    TRACE(printf("trim_arena24()\n"));
    trim_arena24();
    if (pool24 != &dummy_arena) printf("pool24 != &dummy_arena\n");
    if (pool24->next != pool24) printf("pool24->next != pool24\n");
    if (pool24->prev != pool24) printf("pool24->prev != pool24\n");
    TRACE(printf("free_arena24()\n"));
    free_arena24();
    TRACE(printf("done\n"));
    return 0;
}
...