2010-09-22 3 views
142

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

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

также:

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

Много больших ответов, спасибо всем! – AshleysBrain

+4

Есть уже много отличных ответов, но вот несколько фотографий из реального приложения (Firefox), где фрагментация памяти была большой проблемой: http://blog.pavlov.net/2007/11/10/memory-fragmentation/ –

+1

@ MariusGedminas ссылка больше не работает, поэтому важно предоставить краткое резюме вместе со ссылкой или ответить на вопрос с резюме со ссылкой – katta

ответ

218

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

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

Теперь выделить некоторые из них (5 Распределение):

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

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

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

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

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

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

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

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

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

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

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

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

+58

+1 для визуального примера. –

+1

Итак, каждый символ - это байт? Что сделало бы ваше «большое пространство» == 32 байта (я угадываю - не в счет) :) Приятный пример, но упоминание блоков до последней строки было бы полезно. :) – jalf

+0

@jalf: Да. Я не собирался упоминать подразделения вообще, а затем понял, что в конце концов должен был. Работал над этим, пока вы комментировали. –

67

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

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

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

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

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

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

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

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

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

+8

+1. Я просто удалил свой предложенный ответ, потому что ваша метафора «картинки на стене» действительно, действительно хорошая, ясная. – ctacke

+0

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

+1

Я добавил, что .. спасибо Space_C0wb0y –

6

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

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

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

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

3

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

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

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

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

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

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

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

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

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

2

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

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

  1. Там должен быть больше поисков, чтобы найти хорошее место, чтобы придерживаться «больших» объектов. То есть со множеством мелких объектов, разбросанных по нахождению красивого связного фрагмента памяти, при определенных условиях может быть трудно (это экстремально).
  2. Память не является легко читаемой сущностью. Процессоры ограничены тем, сколько они могут держать и где. Они делают это путем обмена страницами, если предмет, в котором они нуждаются, - это одно место, но текущие адреса - это другое. Если вам постоянно приходится менять страницы, обработка может замедляться (опять же, экстремальные сценарии, когда это влияет на производительность.) См. Это сообщение на virtual memory.
13
  • Что такое фрагментация памяти?

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

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

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

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

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

  • Paul R. Wilson, Mark S. Johnstone, Michael Neely и David Boles. Динамическое распределение ресурсов: обзор и критический обзор. В Трудах 1995 года Международный семинар по управлению памятью, Springer Verlag LNCS, 1995
  • Марк С. Джонстер, Пол Р. Уилсон. Проблема фрагментации памяти: решена? В ACM SIG-PLAN Извещения, том 34 № 3, с. 26-36, 1999
  • M.R. Garey, R.L. Graham and J.D. Ullman. Анализ алгоритмов распределения памяти в худшем случае.В четвертом ежегодном симпозиуме ACM по теории вычислений, 1972
1

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

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

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

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

19

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

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

| | | | | | | | | | | 
    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 байт. Вы должны попросить ОС более десяти мегабайт больше виртуальной памяти, чтобы сделать это, даже если вы просто один байт, застенчивый, имея достаточно места.

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

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

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

8

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

3

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

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

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

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

4

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

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

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

Смежные вопросы