Как написать потокобезопасный и эффективный распределитель памяти без блокировки в C?

Как написать потокобезопасный и эффективный, свободный от блокировки распределитель памяти в C? Под эффективным я подразумеваю:

  1. быстрое распределение и освобождение

  2. оптимальное использование памяти (минимальная потеря и отсутствие внешней фрагментации)

  3. минимальные накладные расходы на метаданные

3 ответов


http://www.research.ibm.com/people/m/michael/pldi-2004.pdf

в этой статье представлен полностью свободный от блокировки распределитель памяти. Он использует только широко доступную поддержку операционной системы и аппаратные атомарные инструкции. Оно предлагает гарантированное наличие даже под произвольным потоком прекращение и авария-отказ, и он невосприимчив к dead-lock независимо от политик планирования, и следовательно он может быть использовать даже в обработчиках прерываний и в режиме реального времени приложения не требуя специальной поддержки планировщика. Кроме того, используя некоторые структуры высокого уровня из Hoard, наш распределитель сильно масштабируемо, взрыв космоса пределов к постоянн фактору, и способен избежать ложного обмена...


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

нет никакого ответа; вы будете балансировать ряд проблем. Получить распределитель без блокировки будет практически невозможно, но вы можете либо сделать блокировку рано и нечасто ( выделяя большие пулы для каждого потока ) или вы можете сделать блокировки настолько маленькими и плотными, что они должны быть правильными.


вы можете использовать список блокировки и пару ведер разных размеров.

Так :

typedef struct
{
    union{
        SLIST_ENTRY entry;
    void* list;
};
byte mem[];
} mem_block;

typedef struct
{
    SLIST_HEADER root;
} mem_block_list;

#define BUCKET_COUNT 4
#define BLOCKS_TO_ALLOCATE 16

static mem_block_list Buckets[BUCKET_COUNT];

void init_buckets()
{
    for( int i = 0; i < BUCKET_COUNT; ++i )
    {
        InitializeSListHead( &Buckets[i].root );
        for( int j = 0; j < BLOCKS_TO_ALLOCATE; ++j )
        {
            mem_block* p = (mem_block*) malloc( sizeof( mem_block ) + (0x1 << BUCKET_COUNT) * 0x8 );
            InterlockedPushEntrySList( &Buckets[i].root, &p->entry );
        }
    }
}

void* balloc( size_t size )
{
    for( int i = 0; i < BUCKET_COUNT; ++i )
    {
        if( size <= (0x1 << i) * 0x8 )
        {
            mem_block* p = (mem_block*) InterlockedPopEntrySList( &Buckets[i].root );
            p->list = &Buckets[i];
        }
    }

    return 0;   // block to large
}

void  bfree( void* p )
{
    mem_block* block = (mem_block*) (((byte*)p) - sizeof( block->entry ));
    InterlockedPushEntrySList( ((mem_block_list*)block)->root, &block->entry );
}

SLIST_ENTRY, InterlockedPushEntrySList, InterlockedPopEntrySList, InitializeSListHead являются функциями для операций с одним связанным списком без блокировки в Win32. Используйте соответствующие операции на других ОС.

недостатки :

  • накладные расходы sizeof( SLIST_ENTRY )
  • ведра могут эффективно расти только один раз в начале, после этого вы можете запустить из памяти и должны спросить OS/другие ведра. (Другие ведра приводит к фрагментации)
  • этот образец немного слишком прост и должен быть расширен, чтобы обрабатывать больше случаев