В malloc, зачем вообще использовать brk?Почему бы просто не использовать mmap? - PullRequest
7 голосов
/ 20 апреля 2019

Типичные реализации malloc используют brk / sbrk в качестве основного средства получения памяти от ОС. Однако они также используют mmap для получения фрагментов для больших выделений. Есть ли реальная польза от использования brk вместо mmap или это просто традиция? Разве это не сработало бы так же хорошо, если бы все это делалось с mmap?

(Примечание: здесь я использую sbrk и brk взаимозаменяемо, потому что они являются интерфейсами для одного и того же системного вызова Linux, brk.)


Для справки, вот пара документов, описывающих glibc malloc:

Справочное руководство по библиотеке GNU C: Распределитель GNU
https://www.gnu.org/software/libc/manual/html_node/The-GNU-Allocator.html

glibc wiki: Обзор Malloc
https://sourceware.org/glibc/wiki/MallocInternals

То, что описывают эти документы, это то, что sbrk используется, чтобы требовать первичной арены для небольших распределений, mmap используется, чтобы требовать вторичных арен, и mmap также используется, чтобы требовать места для больших объектов («гораздо больше»). чем страница ").

Использование как кучи приложения (заявлено с sbrk), так и mmap вносит дополнительную сложность, которая может быть ненужной:

Allocated Arena - основная арена использует кучу приложения. Другие арены используют кучи mmap'd. Чтобы отобразить кусок в кучу, вам нужно знать, какой случай применим. Если этот бит равен 0, фрагмент поступает с главной арены и основной кучи. Если этот бит равен 1, порция поступает из памяти mmap, и местоположение кучи может быть вычислено из адреса порции.

[Glibc malloc получен из ptmalloc, который был получен из dlmalloc , который был запущен в 1987 году.]


Справочная страница jemalloc (http://jemalloc.net/jemalloc.3.html) говорит следующее:

Традиционно, распределители использовали sbrk (2) для получения памяти, которая является неоптимальной по нескольким причинам, включая условия гонки, повышенную фрагментацию и искусственные ограничения на максимальное использование памяти. Если sbrk (2) поддерживается операционной системой, этот распределитель использует как mmap (2), так и sbrk (2) в указанном порядке предпочтения; в противном случае используется только mmap (2).

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

[Написание jemalloc началось в 2005 году.]

ОБНОВЛЕНИЕ: Думая об этом больше, этот бит о «в порядке предпочтения» дает мне строку запроса. Почему порядок предпочтений? Они просто используют sbrk в качестве запасного варианта в случае, если mmap не поддерживается (или не имеет необходимых функций), или возможно, чтобы процесс перешел в состояние, в котором он может использовать sbrk, но не mmap? Я посмотрю на их код и посмотрю, смогу ли я выяснить, что он делает.


Я спрашиваю, потому что я внедряю систему сборки мусора в C, и до сих пор не вижу причин использовать что-либо кроме mmap. Мне интересно, есть ли что-то, чего я пропускаю.

(В моем случае у меня есть еще одна причина избегать brk, которая заключается в том, что в какой-то момент мне может понадобиться malloc)

Ответы [ 4 ]

5 голосов
/ 20 апреля 2019

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

Это было точнотакая же форма с 1975-х годов Unix V6.Напомним, V6 поддерживает адресное пространство пользователя в 65 535 байт.Таким образом, не уделялось много внимания управлению гораздо больше, чем 64 КБ, конечно, не терабайтам.

Использование mmap кажется разумным, пока я не задумываюсь, как измененная или добавленная сборка мусора может использовать mmap но без переписывания алгоритма выделения тоже.

Будет ли это хорошо работать с realloc(), fork() и т. д .?

4 голосов
/ 20 апреля 2019

Очевидным преимуществом является то, что вы можете увеличить последнее распределение вместо , чего вы не можете сделать с mmap(2) (mremap(2) является расширением Linux, а не переносимым).

Для наивных (и не очень наивных) программ, использующих realloc(3) например. добавление к строке приводит к увеличению скорости на 1 или 2 порядка; -)

3 голосов
/ 20 апреля 2019

mmap() не существовало в ранних версиях Unix.brk() был единственным способом увеличить размер сегмента данных процесса в то время.Первая версия Unix с mmap() была SunOS в середине 80-х, первая версия с открытым исходным кодом была BSD-Reno в 1990 году.

А для использования на malloc() вы не хотитетребовать реального файла для резервного копирования памяти.В 1988 году SunOS внедрила /dev/zero для этой цели, а в 1990-х HP-UX внедрил флаг MAP_ANONYMOUS.

В настоящее время существуют версии mmap(), которые предлагают различные методы для выделения кучи.

2 голосов
/ 17 июня 2019

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

Таким образом, независимо от используемой стратегии, вам все равно нужно поддерживать несколько из того, что glibc называет аренами памяти, и руководство GNU упоминает: "Наличие нескольких арен позволяет нескольким потокам выделять память одновременно на отдельных аренах, что повышает производительность. "


Страница справочника jemalloc (http://jemalloc.net/jemalloc.3.html) говорит следующее:

Традиционно, распределители использовали sbrk (2) для получения памяти, которая является неоптимальной по нескольким причинам, включая условия гонки, повышенную фрагментацию и искусственные ограничения на максимальное использование памяти. Если sbrk (2) поддерживается операционной системой, этот распределитель использует как mmap (2), так и sbrk (2) в указанном порядке предпочтения; в противном случае используется только mmap (2).

Я не понимаю, как это применимо к современному использованию sbrk(2), насколько я понимаю. Условия гонки обрабатываются потоковыми примитивами. Фрагментация обрабатывается так же, как и в случае с аренами памяти, выделенными mmap(2). Максимально используемая память не имеет значения, поскольку mmap(2) следует использовать для любого большого выделения, чтобы уменьшить фрагментацию и немедленно освободить память для операционной системы на free(3).


Использование как кучи приложения (заявлено с sbrk), так и mmap вносит дополнительную сложность, которая может быть ненужной:

Allocated Arena - основная арена использует кучу приложения. Другие арены используют кучи mmap'd. Чтобы отобразить кусок в кучу, вам нужно знать, какой случай применим. Если этот бит равен 0, фрагмент поступает с главной арены и основной кучи. Если этот бит равен 1, порция берется из памяти mmap, и местоположение кучи может быть вычислено из адреса порции.

Итак, вопрос в том, что если мы уже используем mmap(2), почему бы просто не выделить арену при запуске процесса с mmap(2) вместо использования sbrk(2)? Особенно, если, как указано, необходимо отслеживать, какой тип распределения использовался. Есть несколько причин:

  1. mmap(2) может не поддерживаться.
  2. sbrk(2) уже инициализирован для процесса, тогда как mmap(2) будет вводить дополнительные требования.
  3. Как говорит glibc wiki , "Если запрос достаточно велик, mmap () используется для запроса памяти непосредственно из операционной системы [...], и может быть ограничение до сколько таких сопоставлений может быть за один раз. "
  4. Карта памяти, выделенная с помощью mmap(2), не может быть расширена так же легко. Linux имеет mremap(2), но его использование ограничивает распределитель ядрами, которые его поддерживают. Предварительное отображение многих страниц с доступом PROT_NONE использует слишком много виртуальной памяти. Использование MMAP_FIXED отменяет отображение любого сопоставления, которое могло быть там раньше, без предупреждения. sbrk(2) не имеет ни одной из этих проблем и явно разработан для безопасного расширения памяти.
...