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


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

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

также:

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

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

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

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

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 и т. д. байтовые границы (т. е. адреса должны быть кратны 2, 4, 8 и т. д.) Это означает, что даже если у вас есть, например, структура, содержащая 3 char поля, ваша структура может иметь размер 12 вместо 3, из-за того, что все поля выровнены по 4-байтовой границе.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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