Yoksel: Блог/2009?/06?/09?/ЭкспериментыСАллокаторами ...
SourceForge.net Logo

Home Page | Изменения / НовыеКомментарии / Справка / Помочь проекту | Вход:  Пароль:  

Блог

Эксперименты с аллокаторами

В посте «Быстрый аллокатор в boost::shared_ptr» я писал, насколько существенно можно уменьшить потребление памяти, если использовать специализированный аллокатор. К сожалению, встроенный в Boost аллокатор не подошел, но интерес к теме остался. В результате я опробовал для Йокселя несколько различных аллокаторов, о чем сейчас расскажу немного подробнее.


Досконального тестирования не проводилось. Несколько раз открывался определенный большой тестовый файл и оценивалась примерная скорость и потребление памяти.

Winnie allocator

Это аллокатор, написанный одним из участников форумов RSDN. Почитать и скачать можно здесь: http://rsdn.ru/forum/src/888215.flat.aspx К сожалению, мне не подошел. Во-первых, он потребляет бОльший объем памяти, чем стандартный аллокатор. При работе через стандартный аллокатор тестовый файл занял в памяти 209Мб, а открытый с использованием данного аллокатора – больше 230. Во-вторых, аллокатор не освобождает память, а переиспользует ранее выделенную.

Loki::SmallObjAllocator

Это аллокатор, приведенный в книге Александреску «Modern C++ Design». Наиболее актуальная версия примеров к книге, оформленная в виде библиотеки находится здесь: http://loki-lib.sourceforge.net/


Кратко напомню организацию этого аллокатора. Аллокатор держит набор пулов. Каждый пул ответственен за выделение участков памяти одного размера. Пул выделяет память суперблоками. В каждом суперблоке размещается несколько конечных блоков памяти, которые и предоставляются приложению.


Наибольшее достоинство данного аллокатора: минимальный оверхед при выделении памяти. После переделки boost::shared_ptr под использование SmallObjAllocator и переопределения операторов new и delete для наиболее используемых классов объем использованной памяти на том же тестовом файле составил всего 175Мб.


Однако недостатки этого аллокатора полностью исключают его использование в реальных проектах. Основной недостаток найти очень просто. Нужно сначала открыть тестовый файл, а потом просто его закрыть. Сначала я подумал, что из-за какого-то бага программа зависла. Ан нет. Просто SmallObjAllocator невообразимо тупит на освобождении памяти. При освобождении менеджер памяти тупо последовательно проходит по всем пулам, а каждый пул тупо последовательно проходит по суперблокам, чтобы определить суперблок, в котором был выделен освобожденный участок памяти. Коротенько об этом упомянуто здесь: http://rsdn.ru/forum/cpp.applied/2597235.flat.aspx В результате, если файл открылся за 5–6 секунд (а операции выделения памяти там не основная часть), то освобождение памяти происходило около 30 секунд. Если же открыть сразу два файла и один закрыть, то можно сразу идти вешаться.


Из других недостатков – это не совсем продуманный дизайн и даже явные ошибки. В качестве примера ошибки рассмотрим метод "SmallObjAllocator::Allocate". Если в процессе выполнения метод не смог выделить память обычным образом, то он вызывает метод “TrimExcessMemory”. Этот метод по логике должен освободить неиспользуемую память и, по идее, этого может хватить, чтобы все-таки выделить память. Из метода “TrimExcessMemory” нас наиболее интересует вызовы “TrimChunkList” для пулов суперблоков. Вот так выглядит “TrimChunkList”:


Как можно видеть, массив чанков копируется в новый массив, а затем свопится с этим новым массивом. Потом при выходе из функции старый массив автоматически уничтожается. Таким образом освобождается неиспользуемая память в std::vector, который, как известно, никогда не уменьшает количество потребляемой памяти (в большинстве реализаций). Однако сам объект FixedAllocator помимо массива чанков еще содержит члены “allocChunk_", “deallocChunk_" и “emptyChunk_". Это указатели на чанки из массива чанков. Они используются для оптимизации операций выделения/освобождения памяти. Как можно видеть, в методе “TrimChunkList” эти члены никак не затрагиваются. Соответственно, после выхода из метод существующие указатели на чанки будут указывать на уже удаленную память.


Также есть некоторая непродуманность дизайна. Например, метод "FixedAllocator::Deallocate" ВСЕГДА вызываются с хинтом – указателем на чанк, из которого требуется освобождать память. В результате все навороты по оптимизации освобождения («deallocChunk_", “VicinityFind”) попросту никак не задействуются. При освобождении основной класс вызывает "FixedAllocator::HasBlock", который последовательно линейно обходит все чанки. Найденный блок уже передается в "FixedAllocator::Deallocate".


Так как, не смотря на проблемы, аллокатор имеет очень небольшой оверхед по потреблению памяти, то возникла мысль слегка его модифицировать. В первую очередь напрашивалось решение хранить чанки в массиве, отсортированном по адресу суперблока. Тогда для поиска нужного суперблока можно использовать бинарный поиск. Это помогло – освобождение стало достаточно быстрым. Однако модфикация привела к замедлению операций выделения памяти. В самом деле. Если нужно добавить новый суперблок, то в худшем случае понадобится вставка чанка внутрь массива со сдвигом всего хвоста. Аналогично, если удаляется суперблок из середины массива. Все это привело постепенному замедлению операций с памятью по мере роста количества выделенной памяти. Например, первое открытие некоторого другого большого тестового файла занимает 2 секунды. Открытие 10-го файла – уже 12 секунд. Поэтому решение с отсортированным массивом – не приемлемо. Вероятно, стоит попробовать задействовать, скажем, std::set... Но это уже выходит за рамки экспериментов с аллокаторами...


В общем, я не понимаю, почему "Loki::SmallObjAllocator" так распиарен. Ничего хорошего в нем нет. Не советовал бы с ним связываться. Разве что его можно использовать в учебных целях или частично как идею для реализации собственного аллокатора. Готовым отлаженным решением он не является. Александреску недооценил задачу.

dlmalloc

Почитать про него и скачать можно здесь: http://g.oswego.edu/dl/html/malloc.html Судя по всему, этот аллокатор имеет репутацию самого используемого, надежного и отлаженного. Подтверждаю, эта репутация полностью заслужена. Аллокатор очень быстрый. Даже при том несовершенном способе тестирования, что я использовал, наблюдается явное ускорение открытия большого файла процентов на 30% (!) Аллокатор имеет меньший оверхед, чем стандартный. При использовании стандартного файл занимает в памяти 209Мб. При использовании “dlmalloc” – 192. Это, конечно, похуже, чем у Александреску, но “dlmalloc” хотя бы работает. Операции по работе с памятью совершенно не замедляются с ростом объема выделенной памяти. Как открывался файл за 2 секунды, так и открывается, даже если открыть 10 файлов. Аналогично с освобождением – никаких тормозов.


В общем, я бы уже с радостью пересел на “dlmalloc” если бы не одно «но»... Этот аллокатор очень неохотно отдает память обратно системе. Например, на моем любимом тестовом файле если просто его открыть/закрыть, то при стандартном аллокаторе будет сначала выделено 209Мб, а после закрытия останется выделено 44Мб. В случае “dlmalloc” выделено останется больше 80. Если файл открыть и закрыть несколько раз, то объем занятой памяти в случае “dlmalloc” будет постепенно увеличиваться и со временем перерастет 100Мб. Если открыть файлов «по максимуму» – на 1.8Гб, то после закрытия всех файлов останется выделенным 1.4Гб. Это не утечка – можно будет опять открыть все файлы и потребляемый объем будет примерно таким же, что в первый раз. Это, очевидно, такая оптимизация. Поэтому аллокатор не получится использовать в Йокселе, т.к. тот же 1С очень часто используется на терминальных серверах, а там такая жропамятность ни к чему. Ну и выделенная аллокатором память будет доступна только Йокселю – 1С или другое приложение-контроллер не сможет получить доступ к этой памяти. Если “dlmalloc” внутри Йокселя съест 1.8Гб, то хостовое приложение может выдать ошибку о нехватке памяти, хотя Йоксель сможет обрабатывать документы запредельных объемов.

Итоги

Так что получается, что Йоксель остается сидеть на стандартном аллокаторе. Очень хотелось бы задействовать “dlmalloc” – уж очень он быстрый. Но возможно это только разве что для standalone-варианта. Да и то только после того, как я встрою в него скриптовый движок, возможность работы с БД и поддержку диалоговых форм :) Пока standalone-вариант в основном применим в качестве простого вьюера и значительное потребление памяти для него не желательно.


Update. Впрочем, частично наезды на “SmallObjAllocator” можно снять. У “SmallObjAllocator” существует два метода “Deallocate”: без указания размера блока и с указанием. В моих тестах использовался вариант без указания. А он самый медленный. После переделки под второй вариант начали работать оптимизации деаллокации и скорость освобождения памяти существенно увеличилась. Правда, там все равно используется линейный поиск в пуле суперблоков, поэтому эффективным освобождение памяти назвать нельзя.


Результаты тестирования исправленного варианта:


В “dlmalloc” скорость освобождения гораздо выше и почти не зависит от объема выделенной памяти. Скорость всегда (даже в неблагоприятных условиях) остается выше скорости самого быстрого варианта для “Loki”.


В целом я по прежнему считаю "Loki::SmallObjAllocator" малопригодным для использования в реальных проектах.


Ссылок на эту страницу нет


 
Файлов нет. [Показать файлы/форму]
Комментариев нет. [Показать комментарии/форму]