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

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

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

также:

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

11 ответов


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

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

теперь выделите некоторые из них (5 выделений):

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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


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

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

 |   |   |   |   |   |   |   |   |   |   |
   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? Ну, у нас есть четыре байт свободной памяти, но у нас нет четыре!--12-->прилежащей байты памяти свободны, поэтому мы не можем выделить D! Это неэффективное использование памяти, потому что мы должны были иметь возможность хранить D, но мы не смогли. И мы не можем переместить C, чтобы освободить место, потому что очень вероятно, что некоторые переменные в нашей программе указывают на C, и мы не можем автоматически найти и изменить все эти значения.

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

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

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

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

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


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

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

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

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

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

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

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

обновление:
Google TCMalloc: Кэширование Потоков 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-использовать либо небольшой блок-распределитель, либо использовать MallocNextGen. В RedHat Linux распределитель по умолчанию, похоже, довольно хорошо справляется с выделением большого количества небольших блоков. В Windows есть Low-fragmentation Heap и он адресует проблему большого количества небольших распределений.

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


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

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

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

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


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

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

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


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

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

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

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

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

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

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

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

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


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

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

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

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


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

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

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

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

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

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

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