Реализация диспетчера памяти в многопоточном C/С++ с динамическим размером пула памяти?

Фон: Я разрабатываю мультиплатформенную структуру, которая будет использоваться как база для игры и использования/создания инструмента. Основная идея состоит в том, чтобы иметь пул работников, каждый из которых выполняет свою собственную нить. (Кроме того, рабочие также смогут появляться во время выполнения.) Каждый поток будет иметь свой собственный менеджер памяти.

Я давно думал о создании моей собственной системы управления памятью, и я думаю, что этот проект будет идеальным, чтобы, наконец, попробовать. Я считаю, что такая настройка системы из-за типов использования этой структуры часто требует выделения памяти в реальном времени (игры и инструменты для редактирования текстур).

Проблемы:

  • Нет общеприменимого решения (?). Рамка будет использоваться как для игр/визуализации (а не для AAA, а для инди/воспроизведения) и создания инструмента/приложения. Я понимаю, что для разработки игр обычно (по крайней мере для консольных игр) выделять большой кусок памяти только один раз в процессе инициализации, а затем использовать эту память внутри менеджера памяти. Но применим ли этот метод в более общем приложении?

    В игре вы могли бы теоретически знать, сколько памяти вам понадобятся вашим сценам и ресурсам, но, например, приложение для редактирования фотографий будет загружать ресурсы всех разных размеров... Так что в последнем случае размер динамической памяти более динамической памяти "нужно будет? Это приводит меня к следующей проблеме:

  • Перемещение уже выделенных данных и сохранение действительных указателей. Обычно при распределении в куче вы получите простой указатель на кусок памяти. В пользовательском менеджере памяти, насколько я понимаю, подобный подход заключается в том, чтобы вернуть указатель куда-нибудь свободному в предварительно выделенный фрагмент. Но что произойдет, если предварительно выделенный кусок слишком мал и его нужно изменить или даже дефрагментировать? Данные будут необходимы для перемещения в памяти, а старые указатели будут недействительными. Есть ли способ прозрачно обернуть эти указатели каким-то образом, но по-прежнему использовать их как обычно "вне" управления памятью, как если бы они были обычными указателями на С++?

  • Сторонние библиотеки. Если нет способа прозрачного использования настраиваемой системы управления памятью для всех распределений памяти в приложении, каждая сторонняя библиотека, с которой я связываюсь, будет по-прежнему используйте "старые" распределения памяти ОС внутри страны. Я узнал, что для библиотек часто обнаруживают функции для установки пользовательских функций распределения, которые будут использоваться библиотекой, но не гарантируется, что каждая библиотека, которую я буду использовать, будет иметь эту способность.

Вопросы: Возможно ли и возможно реализовать диспетчер памяти, который может использовать динамический блок памяти? Если да, то как будет работать дефрагментация и изменение размера памяти, не нарушая в настоящее время указатели на использование? И, наконец, как лучше всего реализовать такую ​​систему для работы с сторонними библиотеками?

Я также благодарен за любые связанные с ними материалы для чтения, документы, статьи и многое другое!: -)

Ответ 1

  • Подготовить более одного решения и позволить пользователю каркаса принять какой-либо конкретный вариант. Классы политики для общего распределителя, которые вы разрабатываете, сделают это красиво.

  • Хороший способ обойти это - обернуть указатели в классе с перегруженным оператором *. Сделайте внутренние данные этого класса только индексом в пуле памяти. Теперь вы можете просто быстро изменить индекс после того, как фоновый поток скопирует данные.

  • Большинство библиотек good С++ поддерживают распределители, и вы должны их реализовать. Вы также можете перегрузить глобальный новый, чтобы ваша версия использовалась. И имейте в виду, что вам вообще не нужно думать о библиотеке, выделяющей или освобождающей большой объем данных, что обычно является обязанностью клиентского кода.

Ответ 2

Как кто-то, кто ранее написал много менеджеров памяти и реализации кучи для игр AAA для последних нескольких поколений консолей, позвольте мне рассказать вам, что это просто не стоит.

Ваша информация устарела - в эпоху gamecube [около 2003 года] мы использовали то, что вы сказали - выделили большой кусок и вырезали этот кусок вручную, используя настраиваемые алгоритмы, настроенные для каждой игры.

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

По мере того как встроенные менеджеры памяти становятся лучше, становится все труднее делать лучше, чем те, которые, безусловно, в общем случае и близкие для конкретных случаев использования. Doug Lea Allocator [или что-то еще с компиляторами компиляции С++ linux теперь], а самые свежие кучи фрагментации Windows на самом деле очень хороши, и вы бы гораздо лучше инвестировали свое время в другое место.

У меня есть электронные таблицы на работе, измеряющие все виды показателей для всей загрузки распределителей - все большие имена и справедливые числа, которые я собирал за эти годы. И в основном, в то время как специализированные распределители могут выиграть по нескольким метрикам [наименьшая служебная нагрузка на каждый космос, пространственная близость, самая низкая фрагментация и т.д.] Для общих показателей, основные из них просто самые лучшие.

Как пользователь вашей библиотеки, мой личный предпочтительный вариант - вы просто выделяете память, когда вам это нужно. Используйте оператор operator new/the new, и я могу использовать стандартные механизмы С++ для их замены и использовать свою собственную кучу (если у меня ее действительно есть), или, альтернативно, я могу использовать определенные для платформы способы замены ваших распределений (например, XMemAlloc on Xbox). Мне не нужны тегирования [захват стоп-копов намного превосходит, что я могу сделать, если хочу]. Понизьте этот список, вы даете мне интерфейс, который вы вызовете, когда вам нужно выделить память - это просто боль для вас, и я, вероятно, просто передам его на новый оператор. Самое худшее, что вы можете сделать, это "знать лучше" и создавать свои собственные кучи. Если проблема с распределением памяти является проблемой, я бы предпочел, чтобы вы использовали решение, которое использует вся игра, а не сворачивайте свои собственные.

Ответ 3

Если вы хотите написать свой собственный malloc()/free() и т.д., вы, вероятно, должны начать с проверки исходного кода для существующих систем, таких как dlmalloc. Это трудная проблема, хотя для того, что она стоит. Написание собственной библиотеки malloc жестко. Избиение существующих библиотек malloc общего назначения будет еще труднее.

Ответ 4

НЕ ИСПОЛЬЗУЙТЕ ДРУГОГО МЕНЕДЖЕРА ПАМЯТИ.

Невероятно сложно реализовать диспетчер памяти, который не поддается разным типам использования и событиям. Возможно, вы сможете создать конкретный менеджер, который хорошо работает в соответствии с вашими шаблонами использования, но написать тот, который хорошо работает для МНОГИХ пользователей, - это работа на полный рабочий день, которую почти никто не сделал хорошо. Хуже того, фантастически легко реализовать диспетчер памяти, который отлично работает в 99% случаев, а затем 1% от крушения времени или внезапно потребляет большую часть или всю доступную память в вашей системе из-за неожиданной фрагментации кучи.

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

И как только вы его заработаете, кому-то еще понадобятся: куки файлы для проверки против пульсации памяти; отчет об использовании кучи; пулы памяти; бассейны бассейнов; отслеживание утечки памяти и отчетность; аудит кучи; расщепление кусков и коалесценция; нить-локальное хранилище; lookasides; Ошибки и защита страниц на уровне процессора и процесса; установка и проверка и очистка шаблонов "свободной памяти" aka 0xdeadbeef; и все, что я не могу придумать с головы.

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

Если вы УВЕРЕНЫ, вы хотите реализовать свой собственный менеджер памяти (и, надеюсь, вы НЕ уверены, что прочитали это сообщение), подробно прочитайте источники dlmalloc, затем еще лучше прочитайте источники tcmalloc, затем убедитесь, что вы понимаете компромисс производительности при внедрении поточно-безопасного и небезопасного менеджера памяти и почему наивные реализации имеют тенденцию давать плохие результаты.