Что такое фрагментация памяти
Перейти к содержимому

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

  • автор:

Борьба с фрагментацией памяти в ядре Linux

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

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

Тем не менее становится очень сложно выделять непрерывную память из линейной области отображения ядра. Например, трудно выделять структурные объекты через аллокатор – типичная и частая операция в режиме ядра – или работать с буфером прямого доступа к памяти (DMA), который не поддерживает режимы scatter/gather . Подобные операции могут вызывать частое уплотнение памяти, приводящее к колебаниям в быстродействии системы или сбою аллокации. В процессе медленного (slow path) выделения памяти выполняются различные операции, определяемые флагом на странице аллокации.

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

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

Мел Горман в этом плане оказался самым усердным участником проекта. Он привнес в него два набора важных патчей. Первый был включен в Linux 2.6.24 и прошел 28 версий, прежде чем сообщество его приняло. Второй набор был добавлен уже в Linux 5.0 и успешно сократил фрагментацию на 94% в случае машин с одним или двумя сокетами.

В этой статье я представлю вам ряд распространенных расширений для алгоритма двойников (buddy memory allocation), который помогает предотвратить фрагментацию памяти в ядре Linux 3.10, разъясню принципы уплотнения памяти, научу просматривать индекс фрагментации и количественно оценивать излишнюю задержку, вызванную уплотнением памяти.

▍Краткая история дефрагментации

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

Дата публикации Статьи на LWN.net
2004-09-08 Kswapd and high-order allocations
2004-05-10 Active memory defragmentation
2005-02-01 Yet another approach to memory fragmentation
2005-11-02 Fragmentation avoidance
2005-11-08 More on fragmentation avoidance
2006-11-28 Avoiding — and fixing — memory fragmentation
2010-01-06 Memory compaction
2014-03-26 Memory compaction issues
2015-07-14 Making kernel pages movable
2016-04-23 CMA and compaction
2016-05-10 Make direct compaction more deterministic
2017-03-21 Proactive compaction
2018-10-31 Fragmentation avoidance improvements
2020-04-21 Proactive compaction for the kernel

Ну а теперь можно приступать.

▍Алгоритм двойников

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

  • аллокация разделов памяти;
  • выделение наборов страниц для каждого отдельного ЦПУ;
  • группировка по типам миграции.

До версии 4.8 ядро реализовывало переработку страниц на основе зоны, потому что ранний дизайн был ориентирован в основном на 32-битные процессоры, и в нем присутствовало много памяти верхнего уровня. Однако темп устаревания страниц в различных зонах одного узла оказывался несогласованным, что вызывало множество проблем.

За довольно длительный период сообщество добавило немало всяческих патчей, но проблема осталась. В свете все большего использования 64-битных процессоров и больших объемов памяти Мел Громан перенес стратегию возврата страниц из зоны в узел, чем решил проблему. Если для наблюдения за операциями возвращения вы используете инструменты Berkley Packet Filter (BPF), то вам это желательно знать.

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

Группировка по типу перемещения– это метод дефрагментации, о котором я расскажу подробно.

▍Группировка по типу перемещения

Для начала вам нужно понять схему адресного пространства памяти. Каждая архитектура процессора имеет собственное определение. Например, определение для x86_64 находится в mm.txt.

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

  • Нижний уровень: смещение страниц;
  • Уровень 1: непосредственный индекс страницы;
  • Уровень 2: индекс среднего каталога;
  • Уровень 3: индекс верхнего каталога;
  • Уровень 4: индекс каталога 4 уровня;
  • Уровень 5: глобальный индекс страницы.

Пятиуровневый пейджинг в системах Intel

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

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

  1. Выделить новую страницу.
  2. Скопировать данные старой страницы в новую.
  3. Изменить значение записи таблицы страниц первого уровня на новый номер фрейма страницы.

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

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

Тремя наиболее используемыми типами перемещения памяти являются: MIGRATE_UNMOVABLE , MIGRATE_MOVABLE и MIGRATE_RECLAIMABLE . Прочие типы имеют особое назначение, о котором я здесь говорить не буду.

Распределение каждого типа перемещения на каждом этапе можно просмотреть через /proc/pagetypeinfo :

Применяемый к странице флаг аллокации определяет, из какой группы перемещения она аллоцируется. Например, для памяти пользовательского пространства можно использовать __GFP_MOVABLE , а для страниц __GFP_RECLAIMABLE .

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

Приоритеты готовности (standby priorities) трех перечисленных типов перемещения в порядке сверху вниз следующие:

MIGRATE_UNMOVABLE: MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE MIGRATE_RECALIMABlE: MIGRATE_UNMOVABLE, MIGRATE_MOVABLE MIGRATE_MOVABLE: MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE

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

▍Анализ событий внешней фрагментации памяти

В своей предыдущей статье Why We Disable Linux’s THP Feature for Databases я упоминал, что для анализа внешней фрагментации памяти вы можете использовать предоставляемые ядром события ftrace . Процедура в таком случае следующая:

1. Активация событий ftrace :

echo 1> /sys/kernel/debug/tracing/events/kmem/mm_page_alloc_extfrag/enable

2. Начало сбора событий ftrace :

cat /sys/kernel/debug/tracing/trace_pipe> ~/extfrag.log

3. Прекращение сбора нажатием Ctrl+C. Событие содержит множество полей:

Для анализа количества событий внешней фрагментации памяти сосредоточьтесь на тех, которые содержат fallback_order < pageblock order . В среде x86_64 pageblock order равен 9.

4. Очистка события:

echo 0> /sys/kernel/debug/tracing/events/kmem/mm_page_alloc_extfrag/enable

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

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

Уплотнение памяти

До внедрения принципа уплотнения памяти в ядре для дефрагментации использовалось неравномерное возвращение страниц (lumpy reclaim). Тем не менее в версии 3.10 (на данный момент самая распространенная) этот функционал был исключен. Если вам интересно узнать об этой технике побольше, можете почитать материалы, которые я привел в статье A brief history of defragmentation. Здесь же я сразу перейду к теме уплотнения памяти.

▍Внедрение алгоритма

Статья Memory Compaction на LWN.net подробно описывает алгоритмический принцип уплотнения памяти. В качестве простого примера можно взять следующую фрагментированную зону:

Небольшая фрагментированная зона памяти — LWN.net

Белые блоки – это свободные страницы, а красные – выделенные. Уплотнение памяти в отношении этой зоны делится на три основных этапа:

1. Сканирование зоны слева направо в поиске красных страниц с типом MIGRATE_MOVABLE .

Поиск перемещаемых страниц

2. В то же время сканирование зоны справа налево в поиске свободных страниц.

Поиск свободных страниц

3. Сдвиг перемещаемых страниц под свободные для создания непрерывного участка свободного пространства.

Зона памяти после уплотнения

Сам принцип выглядит относительно простым, и ядро также предоставляет /proc/sys/vm/compact_memory для запуска уплотнения памяти вручную.

Однако, как говорилось в начале текущей статьи, уплотнение памяти на практике оказывается на особо эффективным – по меньшей мере, не для наиболее распространенной v3.10 – независимо от ручного или автоматического запуска. Ввиду сопутствующей вычислительной нагрузки эта операция, наоборот, ведет к образованию узкого места.

Хотя участники сообщества не отказались от этой идеи и продолжили ее оптимизировать. Например, позднее в ядро v4.6 был добавлен инструмент kcompactd, а в v4.8 непосредственное уплотнение сделали более определенным.

▍Когда выполняется уплотнение памяти

Я ядре v3.10 эта операция выполняется в любой из следующих ситуаций:

  • Вызов потока kswapd для балансирования зон после провальной аллокации верхнего уровня.
  • Вызов потока khugepaged для слияния мелких участков памяти в большие страницы.
  • Активация уплотнения памяти вручную через интерфейс /proc .

Функционал THP замедляет производительность, поэтому данную опцию рекомендуется отключать. Этот нюанс я здесь разбирать не стану и сосредоточусь в основном на процессе выделения памяти.

Выделение памяти по медленном пути (slow path)

Если при выделении в списках аллокатора доступных страниц не обнаруживается, происходит следующее:

  1. Ядро обрабатывает этот запрос по медленному пути и пытается выделить страницы, используя в качестве порога нижний предел.
  2. Если выделение памяти проваливается, что указывает на небольшой ее недостаток, аллокатор пробуждает поток kswapd для асинхронного возвращения страниц и повторяет попытку их выделения, также используя в качестве порога нижний предел.
  3. Провал и этой операции будет означать уже серьезный недостаток памяти. В таком случае ядро сначала запускает асинхронное уплотнение памяти.
  4. Если и это выделение после асинхронного уплотнения памяти провалится, ядро возвращает память непосредственно.
  5. Если после этого ядро вернет недостаточно свободных страниц для выполнения требований, оно выполняет прямое уплотнение памяти. Если же освободить не удалось ни одной страницы, для возвращения памяти вызывается OOM Killer.

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

▍Анализ уплотнения памяти

Количественная оценка задержки
Как говорилось в предыдущем разделе, ядро при выделении памяти может выполнять ее возвращение или уплотнение. Чтобы облегчить количественную оценку задержки, вызываемой непосредственным возвращением памяти и ее уплотнением, я добавил в проект BCC два инструмента, drsnoop и compactsnoop.

Они оба основаны на событиях ядра и подробно задокументированы, но кое-что я все же хочу пояснить: для снижения затрат на введение Berkeley Packet Filters (BPF) эти инструменты перехватывают задержку каждого соответствующего события. Следовательно, из вывода видно, что каждому запросу памяти соответствует несколько результатов задержки.

Причина такого отношения один-ко-многим в том, что для более старых версий ядра вроде v3.10 неясно, сколько раз ядро попытается вернуть память по медленном пути. Эта неопределенность также приводит к тому, что OOM Killer начинает запускаться слишком рано или слишком поздно. В результате большинство задач на сервере подвешиваются на долгое время.

После внедрения патча mm: fixed 100% CPU kswapd busyloop on unreclaimable nodes в v4.12 максимальное число операций непосредственного возвращения памяти было ограничено до 16. Предположим, что средняя задержка такой операции составляет 10 мс. (Сокращение активных или неактивных таблиц цепочек LRU оказывается затратным для современных серверов с несколькими сотнями гигабайтов ОЗУ. Также накладывается дополнительная задержка, если серверу приходится ожидать обратной записи «грязной» страницы).

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

Просмотр индекса фрагментации
Вернемся к уплотнению памяти. Основная логика этого процесса делится на четыре этапа:

  1. Определение, подходит ли зона памяти для уплотнения.
  2. Установка номера фрейма начальной страницы для сканирования.
  3. Изоляция страниц типа MIGRATE_MOVABLE .
  4. Перемещение страниц типа MIGRATE_MOVABLE в верхнюю часть зоны.

Хорошо, а как ядро определяет, подходит ли зона для уплотнения памяти?

Если вы используете интерфейс /proc/sys/vm/compact_memory для принудительного уплотнения памяти зоны, то ядру нет нужды определять ее пригодность для этой процедуры.

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

Чем ближе полученный индекс к 0, тем выше вероятность провала этой операции ввиду недостатка памяти. Это означает, что в таком случае больше подойдет операция возвращения памяти. Приближение же индекса к 1,000 повышает вероятность провала выделения по причине излишней внешней фрагментации. Следовательно, в данной ситуации также нужно применять возвращение памяти, а не уплотнение.

Выбор ядра в пользу уплотнения или возвращения памяти определяется порогом внешней фрагментации, который можно посмотреть через интерфейс /proc/sys/vm/extfrag_threshold .

Также можно непосредственно посмотреть индекс фрагментации с помощью cat /sys/kernel/debug/extfrag/extfrag_index . Имейте ввиду, что результаты ниже поделены на 1,000:

Плюсы и минусы
Анализ уплотнения памяти можно делать как посредством мониторинга интерфейсов на основе файловой системы /proc , так и с помощью инструментов, основанных на событиях ядра ( drsnoop и compactsnoop ), но каждый из этих способов имеет свои сильные и слабые стороны.

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

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

Противодействие фрагментации памяти

В ядре специально заложены механизмы для работы с медленными бэкенд-устройствами. Например, в нем реализован метод замещения страниц по принципу «второго шанса» (Second chance), а также предустановленный диапазон на основе алгоритма LRU, и нет возможности ограничить выделение части памяти под страничный кэш ( page cache ). Некоторые компании кастомизировали ядро под свои нужды, чтобы ограничить страничный кэш, и даже предлагали внедрение этих версий сообществу, но они были отклонены. Думаю, причина в том, что данный функционал вызывает ряд проблем вроде предустановленных рабочих настроек.

В связи с этим для сокращения частоты операций возвращения памяти и в целях борьбы с фрагментацией будет хорошим решением повысить vm.min_free_kbytes (до 5% от общей памяти). Это косвенно ограничит долю кэша страниц в сценариях с большим числом операций ввода/вывода и в случаях, когда на машине установлено больше 100Гб памяти.

Несмотря на то, что увеличение vm.min_free_kbytes ведет к некоторым затратам памяти, эти затраты оказываются ничтожны. Например, если хранилище сервера имеет объем 256ГБ, и вы установите vm.min_free_kbytes на 4G, то это составит всего 1.5% от общего пространства.

В сообществе, конечно, тоже заметили эту затрату памяти, поэтому в v4.6 был добавлен патч для соответствующей оптимизации.

В качестве альтернативы можно выполнить в нужный момент drop cache , но это способно привести к колебаниям в быстродействии приложения.

Заключение

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

Надеюсь, этот материал оказался вам полезен. Если у вас есть какие-либо мысли по теме управления памятью в Linux, приглашаю к их обсуждению в наше рабочее пространство TiDB Community в Slack.

Прим. пер.: оригинальные статьи за авторством Wenbo Zhang доступны здесь (Часть 1) и здесь (Часть 2).

  • ruvds_перевод
  • linux
  • kernel
  • фрагментация памяти
  • дефрагментация
  • управление памятью

Фрагментация диска и памяти

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

Начнем по порядку.

Фрагментация диска

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

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

Теперь удалим второй файл (желтый цвет).

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

Если мы будем продолжать в том же духе, то мы и получим ту самую фрагментацию — у нас есть свободного места на 6 кубиков, но максимальная длина свободного куска — 2 кубика.

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

Проблемы фрагментации

Почему мы не любим фрагментацию? Да потому, что скорость работы с файлами падает. Невозможность считать файл последовательно приводит к замедлению работы с диском. Все бы ничего, обычным пользователям редко нужна такая высокая скорость работы с диском, потери могли бы быть несущественными. Но дело в том, что в Windows виртуальная память находится на том же самом диске, что и файлы. А Windows использует виртуальную память (или файл подкачки) регулярно. Поэтому в случае сильно фрагментированного диска компьютер может начинать сильно тормозить не только при работе с диском. Между прочим, Линукс сильно выигрывает в этом смысле, для виртуальной памяти там используется отдельный раздел.

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

Дефрагментация

Дефрагментация — процесс устранения фрагментации. Файлы выстраиваются так, чтобы каждый (по возможности) был записан на диске единым куском. В Windows XP ее можно было запустить через «Пуск» — «Программы», — группа «Стандартные», группа «Служебные» — команду «Дефрагментация диска». Ничего сложного в этой утилите нет, нужно только выбрать диск, нажать кнопку анализ, а потом и дефрагментация.

В Windows Vista и Windows 7 дефрагментация может выполняться по расписанию в фоновом режиме. Настройки дефрагментации вы можете найти в том же месте, что и в XP. Однако, такой дефрагментации может не хватать — она запускается со значительными ограничениями. Я могу порекомендовать одну бесплатную программу для дефрагментации (http://www.mydefrag.com/) — она существенно лучше справляется со своей задачей.

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

  • Основы интернета (11)
  • Настройки интернета (17)
  • Настройки Windows (17)
  • Программы для Windows (12)
  • Интересное в интернете (33)
  • Настройка сайта (18)
  • Развлечения в интернете (13)
  • Опасный интернет (23)
  • Развитие интернета (18)
  • Интернет сервисы (29)
  • Линукс для начинающих (8)
  • Деньги в интернете (11)
  • Цифровая фотография (12)

Фрагментация памяти

Как известно, сборщик мусора в C# (точнее, в CLR) время от времени проводит чистку оперативной памяти, освобождая память, занятую переменными, которые больше не используются. Кроме этого он также производит дефрагментацию памяти, «уплотняя» кучу. В связи с этим происходит коррекция ссылок на объекты, пережившие сборку мусора. Вероятно, что-то аналогичное происходит при сборке мусора и в других языках. В С++ нет сборщика мусора. В таком случае, даже если программист не забудет очистить всю память, выделенную ранее, то ее все равно может оказаться недостаточно из-за фрагментации, так как процесс дефрагментации не проводится. То есть возможна парадоксальная ситуация, когда общий размер свободной памяти больше, чем требуется для создания нового объекта, но объект не может быть создан.
Так ли это? Есть ощущение, что я ошибаюсь в своих рассуждениях, но где?

Отслеживать
9,356 4 4 золотых знака 40 40 серебряных знаков 56 56 бронзовых знаков
задан 7 янв 2013 в 20:43
DreamChild DreamChild
36.1k 2 2 золотых знака 44 44 серебряных знака 85 85 бронзовых знаков
В 32-bit архитектурах такое возможно, в 64-bit практически невероятно.
7 янв 2013 в 21:02
7 янв 2013 в 21:06

Что значит «почему же?»? Почему в 64-bit практически невероятно? Например потому, что на создание такой ситуации уйдет слишком много времени. Про винду не знаю, а в линуксе память под большие объекты каждый раз запрашивается у ядра в виде целого числа страниц. Реально ядро может выделять и несмежные физические страницы, которые отображаются в непрерывный диапазон виртуальной памяти. При освобождении они возвращаются и дефрагментации не происходит. Ожидаемой Вами дефрагментации с кусками меньшими 4К добиться, наверное, можно, но такое время программы не живут.

7 янв 2013 в 21:24

@DreamChild Во-первых сборщики мусора под C/C++ есть, например en.wikipedia.org/wiki/Boehm_garbage_collector (про дефрагментацию, разумеется, можно забыть). @avp живут, живут. Гигабайты свопа — величина не бесконечная. 🙂

8 янв 2013 в 3:11

@avp, не догоняю, причем тут количество бит. Ведь реальный физическуий объем памяти ограничен, и как правило, значительно меньше 18 ЭкзаБайт (даже учитывая своп-файл). И ситуация, описанная автором, даже в 64-битной системе вполне реальна — когда останется куча дырок, меньших по размеру, нежели объем объекта, который пытаемся выделить посредством new.

13 авг 2013 в 15:09

5 ответов 5

Сортировка: Сброс на вариант по умолчанию

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

Другое дело, если фрагментация происходит на уровне виртуальной памяти. Это может запросто произойти в программах на С или С++, где происходит многочисленные выделения и удаления небольших фрагментов памяти. Это может привести к сильной утечки памяти (хотя в коде вся выделенная память освобождается!) и, возможно, к исчерпанию всей системной памяти. Но тут уже всему настанет кердык, если система такие ситуации не отслеживает и не выгружает «прожорливые» процессы.

Отслеживать
ответ дан 7 янв 2013 в 22:33
23.9k 2 2 золотых знака 37 37 серебряных знаков 69 69 бронзовых знаков

Да, такое возможно. Причем часто возникает в нагруженных приложениях.

Для решения этой ситуации есть много решений. Например кастомные аллокаторы. Пусть в приложении нужно выделять много раз память под мелкие объекты. Кастомный аллокатор выделяет память немного большего размера (округляя до кратного 2 в степени). Аллокатор при старте выделяет один большой объем памяти и разбивает его на участки для 2в8, для 2в10 (и так далее). При правильном подходе аллокатор хоть и будет тратить больше памяти, но не будет фрагментации. Деструктор не возвразщает память назад системе, а просто помечает как свободной.

Отслеживать
ответ дан 7 янв 2013 в 20:57
112k 6 6 золотых знаков 93 93 серебряных знака 159 159 бронзовых знаков

Вопрос почему-то опять вызвал интерес. Вот взял и попробовал. Всегда приятно узнавать что-то новое.

Оказалось, что в убунте, если soft limit не установлен, то вместо ожидаемого ENOMEM мы получаем SIGKILL от ядра.

На 64-бит виртуалке с гигом ОЗУ и 2.5 гигами свопа при malloc(1000) этот процесс продолжается всего-то 1.5 минуты . (Так что будем считать, что я просто пошутил, утверждая, «что так долго не живут»).

После установки лимита на виртуальную память (800 мегов), malloc все-таки стал возвращать 0 и я проверил вопрос о фрагментации.

Действительно, освободив 80 мегов realloc-ом «по месту» (уменьшая каждый блок на 100 байт), не смог выделить 1000 байт malloc-ом, а по 50 байт удалось получить, как не сложно догадаться, только половину из освобожденной ранее памяти.

(Если тестовая программка кому-то интересна, то напишите, завтра вставлю в дополнение ответа.)

(наверное, лучше поздно, чем никогда -))

/* https://drive.google.com/file/d/0BzY1LBmZNGbwbUYtNS01VWVXUG8/view?usp=sharing show memory fragmentation use ulimit -a for look limits ulimit -S -v NNNNN for see malloc returns 0 and try realloc show or ulimit -S -v unlimited for SIGKILL if no more memory */ #include #include #include #include #include #include #include #include #include #include #include #include struct memblk < struct memblk *next; size_t mbsize; char d[]; >; struct data < void *addr; size_t nb; >; #define M1 (1024 * 1024) #ifndef SWAP_BOUND #define SWAP_BOUND 10 /* когда мы выберем весь freeram и freeswap (величины при запуске программы) кроме SWAP_BOUND доли swap (например 50 это 1/50-я), мы начинаем слать данные о выделяемой памяти по пайпу для печати окончательного результата (поскольку тут уже в любой момент можем получить SIGKILL от ядра) */ #endif sigjmp_buf jmp; int signo = 0; void catch (int sig) < signo = sig; siglongjmp(jmp, sig); >/* время в миллисекундах */ static long long mtime() < struct timeval t; gettimeofday(&t, NULL); long long mt = (long long)t.tv_sec * 1000 + t.tv_usec / 1000; return mt; >int main (int ac, char *av[]) < size_t mbsize = av[1] ? atoi(av[1]) : 1000, n1000 = 0, s1000 = 0; struct memblk *l1000 = 0, *p; struct sysinfo info; sysinfo(&info); printf("total(free)ram: %ld (%ld) total(free)swap: %ld (%ld) (in %d units)\n", info.totalram, info.freeram, info.totalswap, info.freeswap, info.mem_unit); int chan[2]; pipe(chan); struct data tr = ; long long start = mtime(); pid_t child; if (child = fork()) < close(chan[1]); int s, crit = 0; while (read(chan[0], &tr, sizeof(tr)) == sizeof(tr)) crit++; pid_t p = wait(&s); if (crit) printf("Fin %d critical blocks\n" "total %ld blocks %ld bytes (%f MB) %lld msec\n", crit, (long)tr.nb, (long)(tr.nb * mbsize), ((double)(tr.nb * mbsize)) / M1, mtime() - start); else printf("no final critical data %lld msec\n", mtime() - start); if (p == child) if (WIFEXITED(s)) exit(WEXITSTATUS(s)); else raise(WTERMSIG(s)); return puts("unexepcted exit"); >int sig; for (sig = 1; sig < 64; sig++) if (signal(sig, catch) == SIG_ERR) printf ("err signo %d\n", sig); if (sig = sigsetjmp(jmp, 0)) < printf ("catch sig %d (signo %d)\n", sig, signo); exit(0); >int done = 0; while (p = (typeof(p))malloc(mbsize)) < p->mbsize = mbsize; p->next = l1000; l1000 = p; n1000++; s1000 += mbsize; if (n1000 % M1 == 0) printf ("%ld blocks %ld bytes (%f MB) %lld msec\n", (long)n1000, (long)s1000, ((double)s1000) / M1, mtime() - start); else if (n1000 * mbsize > info.freeram * info.mem_unit + info.freeswap * info.mem_unit - info.freeswap * info.mem_unit / SWAP_BOUND) < tr.nb = n1000; tr.addr = p; write(chan[1], &tr, sizeof(tr)); if (!done) done = 1, printf("begin crit: %ld\n", (long)tr.nb); >> printf ("End %ld blocks %ld bytes (%f MB) %lld msec\n", (long)n1000, (long)s1000, ((double)s1000) / M1, mtime() - start); close(chan[0]); close(chan[1]); if (p = malloc(5)) puts ("malloc(5) yes"); printf ("malloc(50) %s\n", malloc(50) ? "yes" : "no"); typeof (p) prev = 0, t; size_t save = 0, d = mbsize / 10; start = mtime(); for (p = l1000; p; p = p->next) < if (t = realloc(p, p->mbsize - d)) < t->mbsize -= d; if (t != p) < puts ("new addr"); if (prev) prev->next = t; else l1000 = t; > prev = p = t; save += d; > else < puts ("can't realloc"); exit(1); >> printf ("realloc d: %ld sum: %ld (%lld msec)\n", (long)d, (long)save, mtime() - start); printf ("malloc(1000) again: %s\n", malloc(mbsize) ? "yes" : "no"); save = 0; while (malloc(50)) save += 50; printf ("malloc(50) = %ld\n", (long)save); return 0; > 

Фрагментация памяти

Кто-нибудь проводил исследования, какие современные языки (компиляторы, рантаймы и пр.) в большей мере страдают от этого? Например, запустили программу, которая обрабатывает массив данных на диске мелкими порциями (0,1-10М для определённости), она приступила, через сутки отъела 2Г памяти и упала. Не потому что кривая, а потому что такой у рантайма такой менеджер памяти. Или не через сутки, а через неделю. Но упадёт гарантировано.

question4 ★★★★★
10.08.17 19:25:42 MSK

quwy
( 10.08.17 19:38:13 MSK )

Управление кэшем в линуксе достаёт своими фрагментациями, особенно при выходе из спячки — ОЗУ хватает, а оно всё равно пихает из него в кэш и создаёт тормоза и глюки. На чём это написано, посмотри сам.

Napilnik ★★★★★
( 10.08.17 19:38:32 MSK )

джаваскрипт наверняка страдает, сам не замерял но по ощущениям так вот чувствуется что вот прям фрагментируется.

А если серьезно — когда я над чем то долго-долго сижу, по 1000+ раз перезапустил страницу с обновленным кодом, так вот через какое то время браузер начинает тупить и грохается даже при том что одна вкладка и вроде как при релоаде старая программа уже всё и её данные уже всё, хотя может это и браузера проблемы скорей.

uin ★★★
( 10.08.17 19:46:17 MSK )
Ответ на: комментарий от uin 10.08.17 19:46:17 MSK

Ужас какой. Что за браузер?

imul ★★★★★
( 10.08.17 21:18:03 MSK )
Ответ на: комментарий от imul 10.08.17 21:18:03 MSK

uin ★★★
( 10.08.17 21:19:49 MSK )

В большей — сложно сказать. А в меньшей — JVM и .NET-based языки должны быть вообще свободны от проблем с фрагментацией.

qrck ★★
( 10.08.17 21:23:30 MSK )

C и плюсы при неосторожном обращении фрагментируют невозбранно.

Valeg ★★★
( 10.08.17 22:00:49 MSK )

программу, которая обрабатывает массив данных на диске мелкими порциями (0,1-10М для определённости), она приступила, через сутки отъела 2Г памяти и упала. Не потому что кривая,

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

lenin386 ★★★
( 10.08.17 22:01:00 MSK )
Последнее исправление: lenin386 10.08.17 22:05:22 MSK (всего исправлений: 3)

Так мелкие порции, это же хорошо. И чем меньше, тем лучше.
Нулевой указатель от malloc получают не когда памяти нет, а когда негде взять кусок запрошенного размера.

А если сборщик мусора в языкнейм старые куски не освобождает (утечка), то какая разница, какого они размера?

aidaho ★★★★★
( 10.08.17 22:04:13 MSK )
Ответ на: комментарий от Valeg 10.08.17 22:00:49 MSK

Фрагментация — естественное свойство динамической памяти вне зависимости от языка. Чудес тут быть не может. Не хочешь неожиданностей — не пользуйся этим. Если иначе нельзя — максимально продумывай последствия (обрабатывай возвращаемые значения блжд)

lenin386 ★★★
( 10.08.17 22:11:14 MSK )
Последнее исправление: lenin386 10.08.17 22:11:34 MSK (всего исправлений: 1)

Ответ на: комментарий от qrck 10.08.17 21:23:30 MSK

должны быть вообще свободны от проблем с фрагментацией

пожалей носки мои.

lenin386 ★★★
( 10.08.17 22:15:11 MSK )
Ответ на: комментарий от Valeg 10.08.17 22:00:49 MSK

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

Iron_Bug ★★★★★
( 10.08.17 22:51:42 MSK )

Фрагментируется-то виртуальная, отъели мы 2 гига или 256, какая разница?

Gary ★★★★★
( 10.08.17 23:18:44 MSK )

a = malloc(n); for (;;n = n

И ничего не поделаешь.

baka-kun ★★★★★
( 10.08.17 23:19:24 MSK )
Ответ на: комментарий от lenin386 10.08.17 22:15:11 MSK

Ну так там сборщик мусора автоматически дефрагментирует память. Все что не припинено явно — может и будет перемещено сборщиком мусора, так что бы в каждой из куч не было дырок

qrck ★★
( 10.08.17 23:58:28 MSK )
Ответ на: комментарий от qrck 10.08.17 23:58:28 MSK

Ну так там сборщик мусора автоматически дефрагментирует память.

Ну, так _когда_ он это делает ? Твоей программе, например, пришло сообщение, что у пациента остановилось сердце. А тут на сцену выходит сборщик мусора.

lenin386 ★★★
( 11.08.17 00:01:31 MSK )
Последнее исправление: lenin386 11.08.17 00:02:48 MSK (всего исправлений: 1)

Ответ на: комментарий от lenin386 10.08.17 22:11:14 MSK

Фрагментация — естественное свойство динамической памяти вне зависимости от языка. Чудес тут быть не может.

Перемещающий сборщик мусора решает проблему фрагментации.

i-rinat ★★★★★
( 11.08.17 00:24:29 MSK )
Ответ на: комментарий от lenin386 10.08.17 22:01:00 MSK

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

Это требования для нишевого софта. Я сомневаюсь, что у меня на компе есть хотя бы одна программа, которая сможет пройти сертификацию по MISRA C или MISRA C++.

i-rinat ★★★★★
( 11.08.17 00:31:04 MSK )
Ответ на: комментарий от i-rinat 11.08.17 00:31:04 MSK

Это требования для нишевого софта

Да любой софт — нишевый.

Я сомневаюсь, что у меня на компе есть хотя бы одна программа, которая сможет пройти сертификацию по MISRA C или MISRA C++.

Сертификация — это тупо покупка бумаги. _Тупо_ _покупка_ _бумаги_.

lenin386 ★★★
( 11.08.17 00:40:28 MSK )
Ответ на: комментарий от lenin386 11.08.17 00:40:28 MSK

Сертификация — это тупо покупка бумаги. _Тупо_ _покупка_ _бумаги_.

И что ты этим доказал? _И_ _что_ _ты_ _этим_ _доказал_?

i-rinat ★★★★★
( 11.08.17 00:48:48 MSK )
Ответ на: комментарий от lenin386 11.08.17 00:01:31 MSK

Ну, так _когда_ он это делает ? Твоей программе, например, пришло сообщение, что у пациента остановилось сердце. А тут на сцену выходит сборщик мусора.

Воспрос был про фрагментацию, а не про стоимость fragmentation-free памяти 😉

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

qrck ★★
( 11.08.17 07:47:13 MSK )
Ответ на: комментарий от aidaho 10.08.17 22:04:13 MSK

Так мелкие порции, это же хорошо. И чем меньше, тем лучше.

Создалась переменная и сразу разбросалась по 1000 фрагментам — что тут хорошего?

Napilnik ★★★★★
( 11.08.17 13:04:08 MSK )
Ответ на: комментарий от quwy 10.08.17 19:38:13 MSK

В смысле? Страдает или не страдает? Где тесты?

question4 ★★★★★
( 11.08.17 14:14:05 MSK ) автор топика
Ответ на: комментарий от qrck 10.08.17 21:23:30 MSK

JVM и .NET-based языки должны быть вообще свободны от проблем с фрагментацией.

Кто-нибудь их тестировал? Как?

question4 ★★★★★
( 11.08.17 14:14:50 MSK ) автор топика
Ответ на: комментарий от lenin386 10.08.17 22:01:00 MSK

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

Сообщения вида «Out of memory exception. Restarting server.» я отнёс к «падениям».

question4 ★★★★★
( 11.08.17 14:17:44 MSK ) автор топика
Ответ на: комментарий от aidaho 10.08.17 22:04:13 MSK

А если сборщик мусора в языкнейм старые куски не освобождает (утечка), то какая разница, какого они размера?

Об этом и вопрос. Какие языки и библиотеки больше грешат утечками и несвоевременным перемещением, и как это проверяют?

question4 ★★★★★
( 11.08.17 14:19:29 MSK ) автор топика
Ответ на: комментарий от Gary 10.08.17 23:18:44 MSK

Фрагментируется-то виртуальная, отъели мы 2 гига или 256, какая разница?

В 32-битных системах сколько памяти на процесс? 2 или 4? Пусть будет не 2, а 4. Или у линуксов и это ограничение расширено?

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

question4 ★★★★★
( 11.08.17 14:24:56 MSK ) автор топика
Ответ на: комментарий от uin 10.08.17 19:46:17 MSK

джаваскрипт наверняка страдает, сам не замерял но по ощущениям так вот чувствуется что вот прям фрагментируется.

1000+ раз перезапустил страницу с обновленным кодом, так вот через какое то время браузер начинает тупить и грохается

Вот из-за него я помимо языков упомянул рантаймы. Уже разбирали, что проблема проявляется только для Фаерфокса в сочетании с glibc. По отдельности, ни код FF, ни GCC, ни glibc в столь страшной фрагментации не замечены.

Но это одиночный пример, а я спрашиваю, были ли системные исследования?

question4 ★★★★★
( 11.08.17 14:29:49 MSK ) автор топика
Ответ на: комментарий от Iron_Bug 10.08.17 22:51:42 MSK

это если программу писал говнокодер

Если программу писал большой коллектив, что-то такое обязательно найдётся.

question4 ★★★★★
( 11.08.17 14:32:48 MSK ) автор топика
Ответ на: комментарий от i-rinat 11.08.17 00:24:29 MSK

Перемещающий сборщик мусора решает проблему фрагментации.

Где он есть? Как он называется по-английски?

question4 ★★★★★
( 11.08.17 14:36:39 MSK ) автор топика
Ответ на: комментарий от question4 11.08.17 14:36:39 MSK

А дальше просто перебираешь алгоритмы, смотришь, перемещают ли они данные. Обычно для каждого алгоритма есть несколько примеров использования. Так для mark-compact написано, что подобные используются в Common Language Runtime (.NET) и Glasgow Haskell Compiler.

i-rinat ★★★★★
( 11.08.17 14:59:31 MSK )
Ответ на: комментарий от question4 11.08.17 14:24:56 MSK

32-битных системах сколько памяти на процесс? 2 или 4?

прозрачно, без костылей — 2.

lenin386 ★★★
( 11.08.17 15:46:43 MSK )
Ответ на: комментарий от Napilnik 11.08.17 13:04:08 MSK

Создалась переменная и сразу разбросалась по 1000 фрагментам — что тут хорошего?

Сама создалась и сама разбросалась? Ох уж эти пасквилярты-теоретики. Вы хуже диванных специалистов.

andreyu ★★★★★
( 11.08.17 15:59:10 MSK )
Ответ на: комментарий от lenin386 10.08.17 22:01:00 MSK

Результат, возвращаемый функциями выделения и (ты будешь смеяться) освобождения памяти, надо обрабатывать,

void free(void *ptr);

И какой результат ты тут собрался обрабатывать?

Zenom ★★★
( 11.08.17 17:59:21 MSK )
Ответ на: комментарий от lenin386 11.08.17 00:01:31 MSK

> Ну так там сборщик мусора автоматически дефрагментирует память

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

Zenom ★★★
( 11.08.17 18:01:32 MSK )
Ответ на: комментарий от lenin386 11.08.17 15:46:43 MSK

aidaho ★★★★★
( 11.08.17 18:32:53 MSK )
Ответ на: комментарий от aidaho 11.08.17 18:32:53 MSK

# include # include void main (void) < void * rv; for (int i=0; i> 

lenin386 ★★★
( 11.08.17 18:49:40 MSK )
Последнее исправление: lenin386 11.08.17 18:50:49 MSK (всего исправлений: 1)

Ответ на: комментарий от lenin386 11.08.17 18:49:40 MSK

Где ты нашёл линукс с 2G/2G сплитом?

aidaho ★★★★★
( 11.08.17 19:06:33 MSK )
Ответ на: комментарий от i-rinat 11.08.17 14:59:31 MSK

These are called «non-moving» and «moving» (or, alternatively, «non-compacting» and «compacting») garbage collectors

Спасибо за ссылку на раздел.

question4 ★★★★★
( 12.08.17 04:25:35 MSK ) автор топика
Ответ на: комментарий от Napilnik 11.08.17 13:04:08 MSK

Создалась переменная и сразу разбросалась по 1000 фрагментам

i-rinat ★★★★★
( 12.08.17 12:53:21 MSK )
Ответ на: комментарий от question4 11.08.17 14:24:56 MSK

32-битные системы сейчас наверное редкость

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

Не совсем понятен расклад, как это происходит

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

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

Gary ★★★★★
( 12.08.17 14:02:58 MSK )
Ответ на: комментарий от question4 11.08.17 14:32:48 MSK

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

Iron_Bug ★★★★★
( 12.08.17 14:21:30 MSK )
Ответ на: комментарий от Gary 12.08.17 14:02:58 MSK

Если процесс высвобождает память кусочков, которые считал и они ему больше не нужны, то как он сожрёт всю память?

Аллокатор в Glibc устроен немного не так. Для маленьких аллокаций двигается граница памяти процесса. Её можно подвинуть обратно, но только до первого использованного куска. Нельзя просто отдать системе часть этой памяти из середины, можно только надеяться, что система сбросит эти участки в своп, когда понадобится память. Из-за особенностей работы кода управления памятью ядро Linux с неохотой отбирает память, которая активно использовалась процессом. Так что сначала вымоется дисковый кеш, и только потом память начнут забирать у процессов. И то с неохотой. Примерно в это время ты словишь неслабые такие тормоза от свопа.

Если процесс 32-битный, у него ещё и адресное пространство кончится.

Вот тебе пример для экспериментов:

#include #include static void printmaps(void) < char buf[4000]; FILE *fp = fopen("/proc/self/maps", "rb"); printf("\033[2J\033[0;0H"); while (!feof(fp)) < int n = fread(buf, 1, sizeof(buf), fp); fwrite(buf, 1, n, stdout); >fflush(stdout); fclose(fp); > int main(void) < while (1) < for (int k = 0; k < 0x7ffff; k++) malloc(50); printmaps(); >> 

Обрати внимание на то, что растёт только память [heap].

i-rinat ★★★★★
( 12.08.17 16:46:25 MSK )
Ответ на: комментарий от i-rinat 12.08.17 16:46:25 MSK

Отдавать память из середины назад в систему не обязательно, если мы будем «освобождать» её через free, то тогда аллокатор glibc должен спокойно повторно её использовать при следующей аллокации, и размер блока бесконтрольно расти не должен.

Ну и вернуть память из середины всё-таки наверное можно через madvise, насколько я знаю он вызывается при вызове malloc_trim. Я правда не знаю, насколько это верно про то что кеш вымоется раньше чем высвободятся такие страницы, но что-то то что они в своп будут сброшены, мне не верится совсем.

Вообще мне что-то интересно стало, вроде есть такой /proc/[pid]/pagemap, где можно посмотреть реальное распределение страниц памяти процесса, осталось его считать и в виде картинки показать :^)

Gary ★★★★★
( 12.08.17 18:21:18 MSK )
Ответ на: комментарий от Gary 12.08.17 18:21:18 MSK

если мы будем «освобождать» её через free, то тогда аллокатор glibc должен спокойно повторно её использовать при следующей аллокации, и размер блока бесконтрольно расти не должен.

Вообще-то фрагментация — она как раз об этом. Мелкие промежутки свободной памяти не вмещают блок нужного размера, поэтому аллокатор двигает границу снова.

Ну и вернуть память из середины всё-таки наверное можно через madvise, насколько я знаю он вызывается при вызове malloc_trim.

Там MADV_DONTNEED, владение памятью сохраняется. Ну и нужно, чтобы свободный блок покрывал хотя бы одну страницу.

но что-то то что они в своп будут сброшены, мне не верится совсем.

Ну вообще-то ядро обязано сохранить данные в этой памяти. Можно, конечно, дырку пробить, но в glibc этого не делают. Интересно, чего больше от пробивания дырок будет, проблем или пользы?

Вообще мне что-то интересно стало, вроде есть такой /proc/[pid]/pagemap, где можно посмотреть реальное распределение страниц памяти процесса, осталось его считать и в виде картинки показать :^)

Есть утилита pagemon.

i-rinat ★★★★★
( 12.08.17 18:52:30 MSK )
Ответ на: комментарий от i-rinat 12.08.17 18:52:30 MSK

Вот кстати посмотрел через pagemon, malloc_trim «пробивает дырку»

#include #include #include #include #include int main(void) < std::cout addr; addr.reserve(sz); for (int i = 0; i < sz; i++) < addr.push_back(new char[32]); >system("read"); for (int i = 1; i < sz - 1; i++) < delete[](addr.at(i)); >system("read"); malloc_trim(0); system("read"); > 

Gary ★★★★★
( 12.08.17 19:46:05 MSK )
Последнее исправление: Gary 12.08.17 19:49:18 MSK (всего исправлений: 1)

Ответ на: комментарий от i-rinat 12.08.17 18:52:30 MSK

Вот кстати смотрю в madvise про DONTNEED

After a successful MADV_DONTNEED operation, the semantics of memory access in the specified region are changed: subsequent accesses of pages in the range will succeed, but will result in either repopulating the memory contents from the up-to-date contents of the underlying mapped file (for shared file mappings, shared anonymous mappings, and shmem-based techniques such as System V shared memory segments) or zero- fill-on-demand pages for anonymous private mappings.

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *