Подтвердить что ты не робот

Работа с фрагментацией в пуле памяти?

Предположим, что у меня есть объект пула памяти с конструктором, который принимает указатель на большой кусок памяти ptr и размер N. Если я делаю много случайных распределений и освобождений разных размеров, я могу получить память в таком состоянии, что я не может выделить объект M-байта в памяти, хотя может быть много свободного! В то же время я не могу сжать память, потому что это вызовет зависание указателей на потребителей. Как разрешить фрагментацию в этом случае?

4b9b3361

Ответ 1

Я хотел добавить свои 2 цента только потому, что никто другой не указал, что из вашего описания это похоже на то, что вы используете стандартный распределитель кучи (то есть, что все мы уже используем каждый раз, когда вызываем malloc() или оператор new).

Куча - это именно такой объект, который идет в диспетчер виртуальной памяти и запрашивает большой объем памяти (что вы называете "пулом" ). Тогда у него есть всевозможные различные алгоритмы для решения наиболее эффективного способа выделения различных кусков размера и их освобождения. Кроме того, многие люди модифицировали и оптимизировали эти алгоритмы на протяжении многих лет. В течение долгого времени Windows поставлялась с опцией, называемой кучей с низкой фрагментацией (LFH), которую вы использовали для включения вручную. Начиная с Vista LFH по умолчанию используется для всех куч.

Кучи не идеальны, и они могут определенно увязать производительность, когда они не используются должным образом. Поскольку поставщики ОС не могут предвидеть каждый сценарий, в котором вы будете использовать кучу, их кучевые менеджеры должны быть оптимизированы для "среднего" использования. Но если у вас есть требование, которое аналогично требованиям для обычной кучи (т.е. Многих объектов различного размера....), вы должны просто использовать кучу, а не изобретать ее повторно, потому что вероятность того, что ваша реализация будет уступать той ОС уже предоставляет вам.

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

Например, в нашем приложении у нас было требование для многих распределений менее 1 КБ, но эти распределения использовались только в течение очень коротких периодов времени (миллисекунды). Чтобы оптимизировать приложение, я использовал библиотеку Boost Pool, но расширил ее так, чтобы мой "распределитель" фактически содержал коллекцию объектов расширенного пула, каждый из которых отвечал за выделение одного определенного размера от 16 до 1024 (с шагом 4). Это обеспечило почти бесплатную (O (1) сложность) размещение/отсутствие этих объектов, но улов в том, что а) использование памяти всегда велико и никогда не опускается, даже если у нас нет одного выделенного объекта; b) Boost Pool never освобождает память, которую он использует (по крайней мере, в режиме, в котором мы его используем), поэтому мы используем это только для объектов, которые не прилипают очень долго.

Итак, какой аспект нормального распределения памяти вы готовы отказаться от своего приложения?

Ответ 2

В зависимости от системы есть несколько способов сделать это.

Старайтесь избегать фрагментации в первую очередь, если вы выделяете блоки по степеням 2, у вас меньше шансов вызвать такую ​​раздробленность. Есть несколько других способов обойти это, но если вы когда-либо достигнете этого состояния, тогда вы просто OOM в этот момент, потому что нет деликатных способов обращения с ним, кроме как убить процесс, который запрашивает память, блокируя, пока вы не сможете выделить память, или возвращая NULL в качестве области выделения.

Другой способ - передать указатели на указатели ваших данных (например: int **). Затем вы можете изменить память под программой (надеюсь, надеюсь, что потокобезопасность) и скомбинировать распределения, чтобы вы могли выделять новые блоки и сохранять данные из старых блоков (как только система попадает в это состояние, хотя это становится тяжелым накладным расходами, но должно редко сделайте).

Существуют также способы "бининга" памяти, так что у вас есть смежные страницы, например, выделяют 1 страницу только для распределений 512 и меньше, другой для 1024 и менее и т.д. Это упрощает принятие решений о том, какие bin для использования, и в худшем случае вы разделились из следующего самого большого бина или слились из нижнего бункера, что уменьшает вероятность фрагментации на нескольких страницах.

Ответ 3

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

Ответ 4

Было бы полезно узнать более точно, что вы на самом деле пытаетесь сделать, потому что есть много способов справиться с этим.
Но, первый вопрос: действительно ли это происходит, или это теоретическая проблема?

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

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

Ответ 5

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