您的应用程序分配了许多 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;
}