Что такое фрагментация памяти? - PullRequest
181 голосов
/ 22 сентября 2010

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

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

Также:

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

Ответы [ 11 ]

281 голосов
/ 22 сентября 2010

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

70 голосов
/ 22 сентября 2010

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

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

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

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

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

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

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

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

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

23 голосов
/ 22 сентября 2010

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

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

 |   |   |   |   |   |   |   |   |   |   |
   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 или нет.

14 голосов
/ 22 сентября 2010
  • Что такое фрагментация памяти?

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

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

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

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

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

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

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

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

6 голосов
/ 13 октября 2015

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

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

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

6 голосов
/ 22 сентября 2010

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

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

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

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

3 голосов
/ 22 сентября 2010

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

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

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

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

3 голосов
/ 22 сентября 2010

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

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

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

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

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

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

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

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

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

2 голосов
/ 22 сентября 2010

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

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

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