Использование распределителей для замены malloc () / free ()? - PullRequest
3 голосов
/ 19 июня 2011

Есть ли какой-нибудь переносимый способ заменить использование malloc () / free () обертками вокруг STL-подобных распределителей?

Контекст: у меня есть библиотека C, которая позволяет указывать пользовательские malloc () /free () - подобные функции для управления памятью, которые используются в многопоточных контекстах.Оглядываясь на хороший многопоточный распределитель, я обнаружил, что mt_alloc из GCC-libstdc ++ работает очень хорошо для моих рабочих нагрузок.Теперь я хотел бы использовать его в указанной библиотеке C, но как это сделать?

Основная проблема, которую я вижу, заключается в функции deallocate (), которая, в отличие от free (), принимает размервыделенный блок памяти в дополнение к его адресу.Поэтому мне нужно каким-то образом отслеживать размер, связанный с каждым выделением памяти, чтобы он мог быть возвращен в deallocate () при освобождении памяти.Самое простое решение, о котором я подумал, - это сохранить размер выделенной памяти в начале блока памяти, но тогда я не уверен, как решить проблемы с выравниванием, которые могут возникнуть.

Есть ли какое-то простое решение, которое я пропускаю?

Ответы [ 4 ]

4 голосов
/ 19 июня 2011

На моей платформе malloc обеспечивает выравнивание выделенной памяти по 8-байтовой границе. Чтобы имитировать это поведение, используйте allocator<uint64_t>:

#include <stdint.h>
#include <ext/mt_allocator.h>

static __gnu_cxx::__mt_alloc<uint64_t> theAllocator;

void* mtmalloc(size_t size)
{
    // Divide size by sizeof(uint64_t) and round up
    size_t payloadElementCount = (size + sizeof(uint64_t) - 1) /
                                 sizeof(uint64_t);

    // Add an extra uint64_t to store the chunk size
    size_t chunkElementCount = 1 + payloadElementCount;

    // Allocate the chunk
    uint64_t* chunk = theAllocator.allocate(chunkElementCount);

    // Store the chunk size in the first word
    chunk[0] = chunkElementCount;

    // Return a pointer past where the chunk size is stored
    return static_cast<void*>(chunk + 1);
}

void mtfree(void* pointer)
{
    // The chunk begins one word before the passed in pointer
    uint64_t* chunk = static_cast<uint64_t*>(pointer) - 1;

    // Retrieve the chunk size
    size_t chunkElementCount = chunk[0];

    // Deallocate the chunk
    theAllocator.deallocate(chunk, chunkElementCount);
}

int main()
{
    int* array = (int*)mtmalloc(sizeof(int) * 4);
    array[0] = 0;
    array[1] = 1;
    array[2] = 2;
    array[3] = 3;
    mtfree(array);
}

Для вашей платформы замените uint64_t на соответствующий тип.

Вы должны проверить это с чем-то вроде Valgrind, чтобы убедиться, что нет утечек памяти!


Вместо uint64_t вы можете использовать __BIGGEST_ALIGNMENT__ GCC и черту типа aligned_storage Boost для решения, переносимого в компиляторы GCC:

typedef boost::aligned_storage<__BIGGEST_ALIGNMENT__, __BIGGEST_ALIGNMENT__> AlignedType;

1 голос
/ 19 июня 2011

Есть замечательная серия об этом, написанная altdevblogaday Полом Лаской.Вот ссылка на первую статью: http://altdevblogaday.org/2011/04/11/ready-set-allocate-part-1/

В этой статье он заботится о распределении блоков по размерам и вопросах выравнивания.Это должно обеспечить хорошо продуманное и хорошо написанное решение для решения ваших проблем.

0 голосов
/ 19 июня 2011

Два основных метода отслеживания размера объекта, о которых я знаю, неявно заключаются в выделенном по размеру распределителе с метаданными в стороне (например, в распределителе в стиле Кингсли) или в указании размера перед объектом какзаголовок объекта (например, dlmalloc).Довольно ужасным третьим решением было бы сохранение карты каждого выделенного объекта и его размера.Эта карта, конечно, будет управляться другим распределителем.

Я думаю, что вы на правильном пути, и это хорошо, что вы знаете о соображениях выравнивания.Я попытался найти некоторую информацию на mt_alloc, чтобы узнать, есть ли альтернативы или сюрпризы, но такая информация не всегда легко получить.У некоторых распределителей есть метод запроса размера объекта (который может быть или не быть дешевым).Если функция deallocate требует явной передачи размера, то я полагаю, что такой функции не существует, но вы никогда не узнаете.

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

struct object_header {
    size_t size;
};

void * buf = xxmalloc (2 * alignment + size + sizeof(object_header));
void * alignedPtr = (void *) (((size_t) buf + sizeof(object_header) + alignment - 1) & ~(alignment - 1));

Если mt_alloc не может допустить освобождение объектов на внутренних указателях, то эта схема создает для вас проблему, потому что, добавляя дополнительное пространство длявыравнивание, вы больше не знаете, оригинальный адрес вернулся к вам.В этом случае вам может понадобиться сохранить дополнительное поле в заголовке.

В зависимости от того, как mt_alloc управляет внутренней памятью, добавление дополнительного заголовка также может привести к значительным накладным расходам.В распределенном по размеру распределителе использование этого заголовка может дать вам в 2 раза больше места на объектах до размера страницы, после чего вы можете заплатить до стоимости дополнительной страницы на объект.В других распределителях это может не быть проблемой, но на это нужно обратить внимание.

0 голосов
/ 19 июня 2011

См. мой ответ здесь относительно хранения значения в начале блока.Вы можете немного изменить его для своих нужд.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...