Что такое фрагментация памяти?

Я слышал, что термин "фрагментация памяти" используется несколько раз в контексте распределения динамической памяти С++. Я нашел несколько вопросов о том, как иметь дело с фрагментацией памяти, но не может найти прямой вопрос, который касается этого. Итак:

  • Что такое фрагментация памяти?
  • Как я могу определить, является ли фрагментация памяти проблемой для моего приложения? Какая программа, скорее всего, пострадает?
  • Каковы хорошие общие способы борьбы с фрагментацией памяти?

также:

  • Я слышал, что использование динамических распределений может значительно увеличить фрагментацию памяти. Это правда? В контексте С++ я понимаю, что все стандартные контейнеры (std::string, std::vector и т.д.) Используют распределение динамической памяти. Если они используются во всей программе (особенно std::string), скорее всего, проблема с фрагментацией памяти?
  • Как можно разделить фрагментацию памяти в приложении STL-heavy?

Ответ 1

Представьте, что у вас есть "большое" (32 байта) пространство свободной памяти:

----------------------------------
|                                |
----------------------------------

Теперь выделите часть (5 распределений):

----------------------------------
|aaaabbccccccddeeee              |
----------------------------------

Теперь освободите первые четыре выделения, но не пятые:

----------------------------------
|              eeee              |
----------------------------------

Теперь попробуйте выделить 16 байт. К сожалению, я не могу, хотя там почти вдвое больше свободного.

В системах с виртуальной памятью фрагментация представляет собой менее сложную проблему, чем вы думаете, потому что большие распределения должны быть только смежными в виртуальном адресном пространстве, а не в физическом адресном пространстве. Итак, в моем примере, если бы у меня была виртуальная память с размером страницы в 2 байта, я мог бы сделать 16-байтовое распределение без проблем. Физическая память будет выглядеть так:

----------------------------------
|ffffffffffffffeeeeff            |
----------------------------------

тогда как виртуальная память (намного больше) может выглядеть так:

------------------------------------------------------...
|              eeeeffffffffffffffff                   
------------------------------------------------------...

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

Тактика для предотвращения фрагментации памяти на С++, путем размещения объектов из разных областей в зависимости от их размера и/или ожидаемого срока их службы. Поэтому, если вы собираетесь создавать множество объектов и уничтожить их все вместе, выделите их из пула памяти. Любые другие распределения, которые вы делаете между ними, не будут из пула, следовательно, они не будут находиться между ними в памяти, поэтому память не будет фрагментирована в результате.

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

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

Ответ 2

Что такое фрагментация памяти?

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

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

Теперь представьте, что стена - это ваша (куча) память, а изображения - объекты. Эта фрагментация памяти.

Как узнать, является ли фрагментация памяти проблемой для моего приложения? Какая программа, скорее всего, пострадает?

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

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

Каковы хорошие общие способы борьбы с фрагментацией памяти?

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

Ответ 3

Фрагментация памяти - это та же концепция, что и фрагментация диска: это означает, что пространство теряется, потому что используемые области недостаточно плотно собраны вместе.

Предположим, что для простого примера игрушек у вас есть десять байт памяти:

 |   |   |   |   |   |   |   |   |   |   |
   0   1   2   3   4   5   6   7   8   9

Теперь выделите три трехбайтовых блока, имена A, B и C:

 | A | A | A | B | B | B | C | C | C |   |
   0   1   2   3   4   5   6   7   8   9

Теперь освободите блок B:

 | A | A | A |   |   |   | C | C | C |   |
   0   1   2   3   4   5   6   7   8   9

Теперь, что произойдет, если мы попытаемся выделить четырехбайтовый блок D? Ну, у нас есть четыре байта свободной памяти, но у нас нет четырех непрерывных байтов свободной памяти, поэтому мы не можем выделить D! Это неэффективное использование памяти, потому что мы должны были бы хранить D, но мы не смогли. И мы не можем перемещать C, чтобы освободить место, потому что очень вероятно, что некоторые переменные в нашей программе указывают на C, и мы не можем автоматически находить и изменять все эти значения.

Как вы знаете, что это проблема? Наилучшим образом, самым большим признаком является то, что размер вашей виртуальной памяти вашей программы значительно больше, чем объем памяти, который вы фактически используете. В реальном мире у вас будет много более десяти байтов памяти, поэтому D просто получит выделение, начиная с байта 9, а байты 3-5 останутся неиспользованными, если вы позже не выделили что-то три байта или меньше.

В этом примере 3 байта не так много, чтобы тратить их, но рассмотрим более патологический случай, когда два распределения пары байтов, например, на десять мегабайт в памяти, и вам нужно выделить блок размер 10 мегабайт + 1 байт. Вы должны обратиться к ОС за более чем 10 мегабайтами больше виртуальной памяти, чтобы сделать это, даже если вы просто один байт застенчивый от наличия достаточно пространства.

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

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

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

Ответ 4

  • Что такое фрагментация памяти?

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

  • Как я могу определить, является ли фрагментация памяти проблемой для моего приложения? Какая программа, скорее всего, пострадает?

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

  • Каковы хорошие общие способы борьбы с фрагментацией памяти?

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

  • Пол Р. Уилсон, Марк С. Джонстон, Майкл Нили и Дэвид Боулз. Динамическое распределение ресурсов: обзор и критический обзор. В материалах 1995 года Международный семинар по управлению памятью, Springer Verlag LNCS, 1995
  • Марк С. Джонстер, Пол Р. Уилсон. Проблема фрагментации памяти: решена? В сообщениях ACM SIG-PLAN, том 34 № 3, стр. 26-36, 1999
  • M.R. Гари, Р.Л. Грэхем и Дж. Д. Ульман. Анализ алгоритмов распределения памяти в худшем случае. В четвертом ежегодном симпозиуме ACM по теории вычислений, 1972 г.

Ответ 5

Обновление:
Google TCMalloc: Thread-Caching Malloc
Было обнаружено, что неплохо справляется с фрагментацией в продолжительном процессе.


Я разрабатываю серверное приложение, у которого были проблемы с фрагментацией памяти на HP-UX 11.23/11.31 ia64.

Это выглядело так. Был процесс, который делал выделение памяти и освобождение памяти и работал в течение нескольких дней. И даже несмотря на отсутствие утечек памяти, потребление памяти в процессе продолжало расти.

О моем опыте. На HP-UX очень легко найти фрагментацию памяти с помощью HP-UX gdb. Вы устанавливаете точку останова, и когда вы нажимаете на нее, вы запускаете эту команду: info heap и видите все распределения памяти для процесса и общий размер кучи. Затем продолжайте свою программу, а затем через некоторое время вы снова попадете в точку останова. Вы снова делаете info heap. Если общий размер кучи больше, но количество и размер отдельных распределений одинаковы, то, вероятно, у вас проблемы с распределением памяти. Если это необходимо, проверьте несколько раз.

Мой способ улучшить ситуацию состоял в этом. После того, как я провел анализ с помощью HP-UX gdb, я увидел, что проблемы с памятью вызваны тем, что я использовал std::vector для хранения некоторых типов информации из базы данных. std::vector требует, чтобы его данные хранились в одном блоке. У меня было несколько контейнеров на основе std::vector. Эти контейнеры регулярно воссоздавались. Часто возникали ситуации, когда новые записи были добавлены в базу данных, после чего контейнеры были воссозданы. И поскольку обновленные контейнеры были больше, они не вписывались в доступные блоки свободной памяти, а среда исполнения требовала нового большего блока от ОС. В результате, несмотря на отсутствие утечек памяти, потребление памяти процесса увеличилось. Я улучшил ситуацию, когда я сменил контейнеры. Вместо std::vector я начал использовать std::deque, который имеет другой способ выделения памяти для данных.

Я знаю, что одним из способов избежать фрагментации памяти на HP-UX является использование либо Small Block Allocator, либо использование MallocNextGen. В RedHat Linux по умолчанию распределитель, по-видимому, отлично справляется с распределением множества небольших блоков. В Windows есть Low-fragmentation Heap, и он адресует проблему большого количества небольших распределений.

Я понимаю, что в STL-тяжелом приложении вы сначала должны выявлять проблемы. Распределители памяти (например, в libc) фактически справляются с проблемой множества небольших распределений, что характерно для std::string (например, в моем серверном приложении есть много строк STL, но, как я вижу, из info heap они не являются вызывая любые проблемы). Мое впечатление, что вам нужно избегать частых больших распределений. К сожалению, есть ситуации, когда вы не можете их избежать и должны изменить свой код. Как я уже сказал в моем случае, я улучшил ситуацию при переключении на std::deque. Если вы идентифицируете фрагментацию памяти, можно будет говорить об этом более точно.

Ответ 6

Фрагментация памяти чаще всего возникает при распределении и освобождении многих объектов различного размера. Предположим, что в памяти имеется следующий макет:

obj1 (10kb) | obj2(20kb) | obj3(5kb) | unused space (100kb)

Теперь, когда освобождается obj2, у вас есть 120 КБ неиспользуемой памяти, но вы не можете выделить полный блок из 120 кб, потому что память фрагментирована.

Общие методы, чтобы избежать этого эффекта включают кольцевые буферы и объект бассейны. В контексте STL могут помочь методы, такие как std::vector::reserve().

Ответ 7

Подробный ответ на фрагментацию памяти можно найти здесь.

http://library.softwareverify.com/memory-fragmentation-your-worst-nightmare/

Это кульминация 11-летних ответов на фрагментацию памяти, которые я предоставляю людям, задающим мне вопросы о фрагментации памяти на softwareverify.com

Ответ 8

Что такое фрагментация памяти?

Когда ваше приложение использует динамическую память, оно выделяет и освобождает куски памяти. Вначале все пространство памяти вашего приложения является одним непрерывным блоком свободной памяти. Однако, когда вы выделяете и освобождаете блоки разного размера, память начинает фрагментироваться, то есть вместо большого смежного свободного блока и множества смежных выделенных блоков будут выделены выделенные и свободные блоки. Поскольку свободные блоки имеют ограниченный размер, их трудно повторно использовать. Например. вы можете иметь 1000 байт свободной памяти, но не можете выделить память для 100-байтового блока, потому что все свободные блоки имеют длину не более 50 байтов.

Другим, неизбежным, но менее проблематичным источником фрагментации является то, что в большинстве архитектур адреса памяти должны быть выровнены с границами байтов 2, 4, 8 и т.д. (то есть адреса должны быть кратными 2, 4, 8 и т.д.), Это означает, что даже если у вас есть, например, структура, содержащая 3 char поля, ваша структура может иметь размер 12 вместо 3, из-за того, что каждое поле выровнено по 4-байтовой границе.

Как я могу определить, является ли фрагментация памяти проблемой для моего приложения? Какая программа, скорее всего, пострадает?

Очевидным ответом является то, что вы получаете исключение из памяти.

По-видимому, нет хорошего портативного способа обнаружения фрагментации памяти в приложениях на С++. Подробнее см. этот ответ.

Каковы хорошие общие способы борьбы с фрагментацией памяти?

В С++ сложно, так как вы используете прямые адреса памяти в указателях, и у вас нет контроля над тем, кто ссылается на определенный адрес памяти. Поэтому переупорядочение выделенных блоков памяти (как делает сборщик мусора Java) не является вариантом.

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

Ответ 9

Это супер-упрощенная версия для чайников.

По мере создания объектов в памяти они добавляются в конец используемой части в памяти.

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

Это называется фрагментацией.

Ответ 10

Если вы хотите добавить элемент в кучу, то происходит то, что компьютер должен выполнить поиск пространства, подходящего для этого элемента. Поэтому динамические распределения, если они не выполняются в пуле памяти или с объединенным распределителем, могут "замедлять" работу. Для тяжелого приложения STL, если вы выполняете многопоточность, есть Распределитель Hoard или версия TBB Intel.

Теперь, когда память фрагментирована, могут произойти две вещи:

  • Должно быть больше поисков, чтобы найти хорошее место для размещения "больших" объектов. То есть со многими небольшими объектами, разбросанными по нахождению красивой смежной части памяти, при определенных условиях может быть сложно (это экстремально).
  • Память не является легко читаемой сущностью. Процессоры ограничены тем, сколько они могут держать и где. Они делают это путем обмена страницами, если предмет, в котором они нуждаются, - это одно место, но текущие адреса - это другое. Если вам постоянно приходится менять страницы, обработка может замедляться (опять же, экстремальные сценарии, когда это влияет на производительность.) См. Эту публикацию в виртуальной памяти.

Ответ 11

Фрагментация памяти происходит из-за того, что запрашиваются блоки памяти разных размеров. Рассмотрим буфер размером 100 байт. Вы запрашиваете два символа, а затем целое число. Теперь вы освобождаете два символа, затем запрашиваете новое целое число, но это целое не может вписываться в пространство двух символов. Эта память не может быть повторно использована, потому что она не находится в достаточно большом непрерывном блоке для повторного выделения. Кроме того, вы набрали много ресурсов распределителя для ваших символов.

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

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

Что вы должны помнить, так это то, что на 32-битной настольной системе x86 у вас есть целая 2 ГБ памяти, которая разделена на 4KB "страницы" (довольно уверен, что размер страницы одинаковый для всех систем x86). Вы должны будете вызвать некоторую фрагментацию omgwtfbbq, чтобы иметь проблему. Фрагментация действительно является проблемой прошлого, поскольку современные кучи чрезмерно велики для подавляющего большинства приложений, а также распространенность систем, способных противостоять им, таких как управляемые кучи.