1

我的应用程序进行了大量正好 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 字节对齐。

4

2 回答 2

8

如果您要进行大量正好 24 字节的分配,并且这些分配的内存效率是一个问题,那么您根本不应该malloc直接使用. 您应该使用大小为 24 字节的池分配器。

池分配器从堆中分配一大块内存,然后将其划分为您指定大小的固定大小块。这样,您不仅可以避免对齐开销,还可以避免堆用于跟踪空闲数据块的信息的开销。您还可以帮助避免由如此小的分配引起的碎片。释放这些分配非常快(就像制作它们一样)。等等。

使用池分配器,您应该能够准确地分配您需要的内容,而不会干扰您正在使用的库。您可以为 10,000 个 24 字节块分配一块内存,唯一的开销将是跟踪空闲块所需的簿记(如果您很聪明,它可以主要使用空闲块本身)。

于 2020-04-06T23:45:38.687 回答
2

您的应用程序分配了许多 24 字节的对象,并且您希望结合这些目标:

  • 在 16 字节边界上对齐每个 24 字节对象,大概是用 SIMD 指令读取其内容
  • 使用尽可能少的内存,理想情况下每个对象只有 24 个字节。

这些目标是不兼容的:如果对象需要 16 字节对齐,则无论您如何分配内存,它们必须至少相隔 32 字节。

C 库malloc()可能已经在您的系统上强制执行 16 字节对齐(这是 64 位系统对 SIMD 兼容数据的常见要求),但可以将块末尾的 8 字节松弛用于其自己的簿记数据。jemalloc()当然可以。所以开销不是浪费,而是分配算法固有的。

由于对齐约束,在池中分配对象对打包没有帮助。它可能更高效,但现代malloc()实现非常高效,有些确实使用基于线程的池(例如tcmalloc())。

设计自己的分配方案很棘手且容易出错,链接自定义malloc()实现也很重要,因为它可能会导致 C 库函数自己使用malloc(). 我强烈建议不要使用这些方法,除非您非常精通 C 语言并且对您的系统有很好的理解。

有一个改进打包的可能方向:如果您还分配了许多 8 字节对象,则可以将它们交错放置在 32 字节块的组合池中,将前 24 字节用于在 16 字节边界上对齐的 24 字节对象以及在 8 字节边界上对齐的单独 8 字节对象的 8 个剩余字节。

另一种方法是将 24 字节对象的存储拆分为一个 16 字节部分的数组和另一个 8 字节部分的数组,使用相同的索引来访问同一逻辑对象的部分。如果您知道要分配的此类对象的最大数量,这是一个可行的解决方案。您将使用索引值而不是指针来访问这些部分。这可能需要对您的代码进行大量修改。

在当前系统上,内存非常便宜且丰富。除非您以现有已部署的嵌入式系统为目标,否则为您的应用程序指定更多 RAM 是一种简单而有效的方法。

这是一个用于 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;
}
于 2020-04-07T11:10:26.443 回答