Ваше приложение выделяет много 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;
}