Оригинальная ссылка:Tickets.WeChat.QQ.com/Yes/3GG BJ AE UV…
Давно не виделись, старые и новые друзья, я Дабин, я долго готовил эту статью, не откладывая, а делая какие-то другие дела посередине, некоторые задерживая.
эта статьяВ основном представляет распределение памяти Go и управление памятью Go., будет немного участвовать в выделении и освобождении памяти, а также сборке мусора Go.
С очень крупной точки зрения управление памятью в Go выглядит так, как показано на рисунке ниже.Сегодня мы в основном сосредоточимся на части, отмеченной красным.
Дружеское напоминание:
Статья длинновата, рекомендуется сначала собрать, а потом читать, безусловно, хороший материал для изучения управления памятью.
Эта статья основана на версии go1.11.2. Могут быть различия в управлении памятью в разных версиях Go. Например, определения mheap в версиях 1.9 и 1.11 сильно различаются. go, но независимо от того, какую версию go вы используете, это отличный источник, потому что идея и структура управления памятью остаются прежними.
Язык Go отказывается от того, как разработчики управляют памятью в C/C++: активное приложение и активный выпуск, добавляя escape-анализ и GC, освобождая разработчиков от управления памятью, позволяя разработчикам иметь больше энергии, чтобы сосредоточиться на разработке программного обеспечения, а не на основных проблемах с памятью. Это одна из причин, почему Go — высокопродуктивный язык.
Нам не нужно разбираться в управлении памятью, потому что это действительно сложно, но освоение управления памятью позволит вам писать код более высокого качества, а также поможет вам находить ошибки.
В этой статье используется поэтапный подход, и в свою очередь будут представлены базовые знания о хранилище, TCMalloc, «предшественнике» управления памятью Go, затем об управлении и распределении памяти Go и, наконец, краткое изложение. Цель этого состоит в том, чтобы надеяться, что вы сможете лучше мыслить в области кодирования и архитектурного мышления благодаря общему пониманию и мышлению.
Наконец, это не статья об анализе исходного кода, потому что уже есть много статей об анализе исходного кода Go.Эти статьи исходного кода могут помочь вам изучить конкретные инженерные приемы и приемы.В конце статьи будут рекомендованы несколько отличных статей. , Если вы интересуетесь памятью, рекомендуется прочитать каждую статью, выбрать понравившуюся и потратить больше времени на ее изучение.
1. Обзор основ хранения
В этой части мы кратко рассмотрим систему хранения компьютера, виртуальную память, стек и кучу, а также управление памятью в куче.Эта часть важна для понимания и освоения управления памятью Go.Рекомендуется друзьям, которые забыли или незнакомы, не пропускать Это.
пирамида хранения
На этом рисунке изображена система хранения данных компьютера сверху вниз:
- регистры процессора
- Cache
- ОЗУ
- Вспомогательные устройства хранения, такие как жесткие диски
- Внешние устройства, такие как мышь
Сверху вниз скорость доступа снижается, а время доступа увеличивается.
Задумывались ли вы когда-нибудь над следующими двумя простыми вопросами, если нет, подумайте об этом:
- Если ЦП напрямую обращается к жесткому диску, можно ли полностью использовать ЦП?
- Если ЦП напрямую обращается к памяти, можно ли полностью использовать ЦП?
Скорость ЦП высокая, но постоянное хранилище, такое как жесткий диск, очень медленное.Если ЦП напрямую обращается к диску, диск может замедлить скорость ЦП, и общая производительность машины будет низкой.Чтобы сделать из-за разницы в скорости между двумя аппаратными средствами, процессором и между дисками добавляется гораздо более быстрая память, чем диски.
Однако скорость процессора и памяти неодинакова, как видно из приведенного выше рисунка, скорость процессора увеличивается быстро (закон Мура), а скорость памяти увеличивается очень медленно.Хотя скорость ЦП сейчас увеличивается очень медленно, скорость памяти увеличилась ненамного, и разрыв в скорости очень велик., С 1980 года разрыв в скорости между ЦП и памятью постоянно увеличивается. Чтобы компенсировать разницу в скорости между этими двумя аппаратными средствами, между ЦП и памятью добавляется кэш-память, которая быстрее, чем память. данных памяти.Может сократить время доступа процессора к памяти.
Не думайте, что естьCacheВсе хорошо.Скорость ЦП все еще увеличивается, и Кэш тоже меняется.От начального уровня 1, к более позднему уровню 2, к современному Кэшу 3 уровня,(Интересно посмотреть историю кеша).
Трехуровневые кэши: L1, L2 и L3. Их скорости имеют три разных уровня. Скорость L1 является самой быстрой и наиболее близкой к скорости ЦП, которая в 100 раз превышает скорость ОЗУ. Скорость L2 снижается до 25 раз. что из ОЗУ Скорость ближе к скорости ОЗУ.
Видишь ли, ты дошел до всего?Многоуровневый дизайн системы хранения?Сверху вниз скорость становится все ниже и ниже, а время доступа становится все больше и больше.От диска к регистру ЦП верхний уровень можно рассматривать как кеш следующего уровня.
После рассмотрения многоуровневого дизайна давайте взглянем на память, ведь мы статья об управлении памятью.
Виртуальная память
Виртуальная память — важная функция современных операционных систем, которая защищает основную оперативную память и диск от процесса и предоставляет процессу объем памяти, намного превышающий размер физической памяти. Посмотрим на виртуальную памятьмногослойный дизайн.
На приведенном выше рисунке показан процесс доступа к виртуальной памяти для получения данных, когда процесс обращается к данным, когда кэш пропускает попадание.
При доступе к памяти фактическим доступом является виртуальная память.Виртуальная память проверяется через таблицу страниц.Загружен ли адрес виртуальной памяти, к которому осуществляется доступ, в физическую память, если он уже находится в физической памяти, данные физической памяти Если соответствующей физической памяти нет, то загрузить данные с диска в физическую память и обновить адрес физической памяти и адрес виртуальной памяти в таблице страниц.
Есть ли доступ к:Физическая память — это уровень кэш-памяти дискового хранилища..
Кроме того, в эпоху отсутствия виртуальной памяти физическая память совместно используется всеми процессами, и возникает проблема параллельного доступа, когда несколько процессов одновременно обращаются к одной и той же физической памяти.После введения виртуальной памяти каждому процессу нужна собственная виртуальная память, а степень детализации одновременного доступа к памяти может быть снижена с многопроцессорного уровня до многопоточного..
стек и куча
Теперь мы начинаем с виртуальной памяти и идем на один уровень дальше, чтобы увидеть стек и кучу в виртуальной памяти, то есть управление памятью процессом.
На приведенном выше рисунке показано разделение виртуальной памяти процесса.Адреса памяти, используемые в коде, являются адресами виртуальной памяти, а не фактическими адресами физической памяти. Стек и куча — это всего лишь две области памяти в виртуальной памяти с разными функциями:
-
Стек находится по старшему адресу и растет от старшего адреса к младшему.
-
Куча находится по младшему адресу и растет от младшего адреса к старшему.
Стек имеет ряд преимуществ перед кучей.:
- Управление памятью в стеке простое, а выделение происходит быстрее, чем в куче.
- Память стека не нужно освобождать, в отличие от кучи, будь то активная свободная или пассивная сборка мусора, которая требует дополнительных ресурсов ЦП.
- Память в стеке имеет лучшую локальность, а доступ к памяти в куче не такой дружественный.Две части данных, к которым обращается ЦП, могут находиться на разных страницах, и время доступа ЦП к данным может увеличиться.
управление кучей памяти
Давайте сделаем еще один шаг: когда мы говорим об управлении памятью, мы в основном имеем в виду управление памятью кучи, потому что управление памятью стека не требует, чтобы программа беспокоилась об этом. В этом разделе рассматривается управление динамической памятью.Как показано на рисунке выше, оно состоит из трех основных частей:Выделение блоков памяти, освобождение блоков памяти и организация блоков памяти.
При простейшем управлении памятью куча памяти изначально будет полным большим блоком, то есть нераспределенной памятью.При выполнении приложения нераспределенная память будет разбита на небольшой блок памяти (блок), а затем использована Связанный список соединяет все блоки памяти. Некоторая информация требуется для описания основной информации о каждом блоке памяти, такой как размер (размер), используется ли он (используется) и адрес следующего блока памяти (следующий).Фактические данные блока памяти хранятся в данных.
Блок памяти содержит три типа информации, как показано на рисунке ниже, метаданные, пользовательские данные и поля выравнивания.Выравнивание памяти предназначено для повышения эффективности доступа. При подаче заявки на 5-байтовую память на следующем рисунке требуется выравнивание памяти.
Суть освобождения памяти заключается в том, чтобы вынуть из связанного списка используемый блок памяти, а затем пометить его как неиспользуемый.При выделении блока памяти можно сначала найти блок памяти аналогичного размера из неиспользуемого блока памяти.Если не найдено, то Выделите память из нераспределенной памяти.
Проблема фрагментации памяти не рассматривалась в приведенном выше простом дизайне, потому что при непрерывном применении и освобождении памяти в памяти будет большое количество фрагментов, что снизит скорость использования памяти. Чтобы решить проблему фрагментации памяти, можно объединить два последовательных неиспользуемых блока памяти, чтобы уменьшить фрагментацию.
Вышеизложенное является основной идеей управления памятью.Для базового управления памятью, если вы хотите узнать больше, вы можете прочитать эту статью "Writing a Memory Allocator", три картинки в этом разделе тоже из этой статьи.
2. TCMalloc
TCMalloc — сокращение от Thread Cache Malloc, источник управления памятью в Go., управление памятью в Go основано на TCMalloc.С итерацией Go несоответствие между управлением памятью в Go и TCMalloc увеличивается, ноЕго основные идеи, принципы и концепции соответствуют TCMalloc., если вы пропустите TCMalloc и сразу перейдете к управлению памятью Go, вы можете запутаться.
Освойте концепцию TCMalloc,Не нужно обращать внимание на слишком много деталей исходного кода, вы можете заложить прочную основу для освоения управления памятью Go.После того, как основа заложена, знания будут прочными.
В Linux на самом деле существует много библиотек управления памятью, таких как ptmalloc из glibc, jemalloc из FreeBSD, tcmalloc из Google и т. д. Почему так много библиотек управления памятью? По сутиДобивайтесь более высокой эффективности управления памятью при многопоточном программировании.: Ускорение выделения является основной целью.
Как быстрее выделить память?
Мы упоминали ранее:
После введения виртуальной памяти степень детализации проблем одновременного доступа к памяти снижается с уровня нескольких процессов до уровня нескольких потоков.
ЭтоПервый уровень более быстрого выделения памяти.
Все потоки одного и того же процесса совместно используют одно и то же пространство памяти. Когда они обращаются за памятью, они должны быть заблокированы. Если они не блокируются, возникает проблема, что к одной и той же памяти обращаются два потока одновременно.
Какова практика TCMalloc?Предварительно выделяйте кеш для каждого потока. Когда поток подает заявку на небольшую память, он может выделить память из кеша, это имеет 2 преимущества:
- Предварительное выделение кэша для потока требует одного системного вызова.Когда последующие потоки обращаются за небольшой памятью, выделение из кэша выполняется в пользовательском режиме без системных вызовов.Сократите общее время выделения и освобождения памяти, что является вторым уровнем быстрого выделения памяти..
- Когда несколько потоков обращаются за небольшой памятью одновременно, они выделяются из соответствующих кэшей и получают доступ к разным адресным пространствам без блокировки.Детализация одновременного доступа к памяти еще больше снижается, что является третьим уровнем быстрого выделения памяти..
Фундаментальный
Ниже приводится краткое введение в TCMalloc, достаточно подробное для понимания управления памятью в Go.
Отказ от ответственности: я не изучал TCMalloc. Следующее введение основано на официальной информации TCMalloc и информации других блоггеров. Пожалуйста, дайте мне знать, если я ошибаюсь.
В сочетании с приведенным выше рисунком представлены несколько важных концепций TCMalloc:
- Page: Операционная система управляет памятью в единицах страниц, как и TCMalloc, но размер страницы в TCMalloc не обязательно равен размеру в операционной системе, а кратен. "Расшифровка TCMalloc«Размер страницы под x64 составляет 8 КБ.
- Span: Группа последовательных страниц называется Span. Например, может быть Span из 2 страниц или Span из 16 страниц. Span на один уровень выше, чем Page, чтобы облегчить управление областью памяти определенного размера. Span — это TCMalloc Основная единица управления памятью в .
- ThreadCache: Каждый поток имеет свой собственный Кэш. Кэш содержит несколько связанных списков блоков свободной памяти. Каждый связанный список связан с блоком памяти. Размер блоков памяти в одном и том же связанном списке одинаков. Можно также сказать, что память предоставляется в соответствии с размером блока памяти.Блоки разделены на категории, так что свободные блоки памяти могут быть быстро выбраны из соответствующего связанного списка в соответствии с запрошенным объемом памяти. Поскольку каждый поток имеет свой собственный ThreadCache, доступ к ThreadCache осуществляется без блокировки.
- CentralCache: Это общий кеш для всех потоков и сохраненный связанный список свободных блоков памяти.Количество связанных списков такое же, как количество связанных списков в ThreadCache.Когда блока памяти ThreadCache недостаточно, его можно взять из CentralCache.Когда имеется много блоков памяти ThreadCache, их можно поместить обратно в CentralCache. Поскольку CentralCache является общим, доступ к нему заблокирован.
- PageHeap: PageHeap — это абстракция кучи памяти. PageHeap также хранит несколько связанных списков. Связанный список хранит Span. связанный список соответствующего размера. , когда в CentralCache будет больше памяти, он будет помещен обратно в PageHeap. Как показано на рисунке ниже, это список диапазонов из 1 страницы, список диапазонов из 2 страниц и т. д. и, наконец, набор больших диапазонов, который используется для сохранения средних и больших объектов. Нет сомнений, что PageHeap также заблокирован.
Вышеупомянутые малые, средние и большие объекты, а также аналогичные концепции в управлении памятью Go.Давайте посмотрим на определение TCMalloc:
- Размер малого объекта: 0 ~ 256 КБ
- Размер среднего объекта: 257~1 МБ
- Размер большого объекта:> 1 МБ
Процесс выделения небольших объектов: ThreadCache -> CentralCache -> HeapPage.В большинстве случаев кэша ThreadCache достаточно, и нет необходимости обращаться к CentralCache и HeapPage.Распределение без блокировки и без системных вызовов, эффективность выделения очень высоко.
Процесс выделения среднего объекта: вы можете напрямую выбрать соответствующий размер в PageHeap.Максимальный объем памяти, сохраняемый Span из 128 страниц, составляет 1 МБ.
Процесс выделения больших объектов: выберите подходящее количество страниц из большого набора диапазонов, чтобы сформировать диапазон для хранения данных.
Через введение в этот раздел у вас должно быть определенное понимание основной идеи TCMalloc, я предлагаю ознакомиться с вышеприведенным содержанием.
Фотографии в этом разделе взяты из "Расшифровка TCMalloc", права на изображение принадлежат оригинальному автору.
Рекомендуемые статьи
В этой статье мало что рассказывается о TCMalloc.Что важно, так это 3 уровня быстрого выделения памяти, если вы хотите узнать больше, вы можете прочитать статью ниже.
- TCMalloc должен прочитать, С помощью этой статьи вы сможете освоить принцип и производительность TCMalloc, что очень полезно для освоения управления памятью в Go.Хотя управление памятью в Go сильно отличается от управления памятью в TCMalloc, этоИстоки и «направления» управления памятью в Go, эта статья возглавляет дюжину статей об управлении памятью в Go.
- Расшифровка TCMalloc необязательный,Исключительно подробный с большим количеством красивых фотографий, На чтение уходят часы, а на понимание нужно больше времени.После прочтения этой статьи вам не нужно читать другие статьи о TCMalloc.
- Введение в TCMalloc необязательный, который представляет собой китайскую версию документов TCMalloc, большинство из которых переведены с английской версии.Если ваш английский не очень хорош, взгляните.
3. Перейти к управлению памятью
После стольких предзнаменований я, наконец, добрался до сути этой статьи. Предыдущие предзнаменования не маловажны, наоборот, они очень важны.Управление памятью языка го основано на предыдущих базовых знаниях и мышлении об управлении памятью.Если вы пропустите предыдущее содержание, я предлагаю вам оглянуться назад, это может помочь вам лучше. Освойте управление памятью Go.
упомянутый вышеGo управление памятью исходит изTCMalloc, но у него есть на 2 вещи больше, чем у TCMalloc: escape-анализ и сборка мусора, которые являются двумя отличными орудиями для повышения производительности.
В этой главе мы сначала познакомимся с управлением памятью Go и выделением памяти Go, и, наконец, рассмотрим небольшую сборку мусора и освобождение памяти.
Основные понятия управления памятью Go
Предыдущий обзор базовых компьютерных знаний представляет собой нисходящий метод введения от макро к микро, который привлекает внимание к сегодняшней теме.
Многие концепции управления памятью Go уже присутствуют в TCMalloc, смысл тот же, но названия изменились. Позвольте мне сначала дать вам макроизображение и представить его вместе с помощью изображения.
Page
Так же, как Page в TCMalloc, размер 1 страницы в x64 составляет 8 КБ. В нижней части изображения выше светло-голубой прямоугольник представляет страницу.
Span
То же, что Span в TCMalloc,Span — основная единица управления памятью., кодmspan
,Группа последовательных страниц образует Span, поэтому группа непрерывных голубых прямоугольников на приведенном выше рисунке представляет диапазон, состоящий из группы страниц. Кроме того, прямоугольник сиреневого цвета является диапазоном.
mcache
mcache похож на ThreadCache в TCMalloc,Mcache сохраняет Spans разного размера и классифицируется по классу Span.Маленькие объекты выделяют память непосредственно из mcache, который действует как кеш и к которому можно получить доступ без блокировок.
Однако mcache также отличается от ThreadCache: в TCMalloc на каждый поток приходится один ThreadCache, а в Go —Каждый P имеет 1 mcache, так как в программе Go в настоящее время выполняется не более GOMAXPROCS-потоков, поэтому для обеспечения свободного доступа к mcache для каждого потока требуется не более GOMAXPROCS mcache.Выполнение потоков привязано к P, а mcache передается П В самый раз.
mcentral
mcentral похож на CentralCache в TCMalloc,Это кеш, который используется всеми потоками и требует доступа к блокировке., он классифицирует интервалы в соответствии с классом интервала и объединяет их в связанный список.Когда выделяется память определенного уровня диапазона mcache, он будет применяться для диапазона текущего уровня к mcentral.
Однако mcentral также отличается от CentralCache. CentralCache имеет один связанный список для каждого уровня Span, а mcache имеет два связанных списка для каждого уровня Span. Это связано с применением памяти mcache, которое мы объясним позже.
mheap
mheap похож на PageHeap в TCMalloc,Это абстракция кучи памяти, которая организует страницы памяти, запрошенные ОС, в интервалы и сохраняет их.. Когда промежутка mcentral недостаточно, он будет применяться к mheap. Когда промежутка mheap недостаточно, он будет применяться к ОС. Приложение памяти к ОС основано на страницах, а затем организованы запрошенные страницы памяти. в пролеты, что также требует блокировки доступа.
Но mheap также отличается от PageHeap: mheap организует Span в древовидную структуру, а не в связанный список, и по-прежнему представляет собой два дерева, а затем назначает Span для heapArena для управления, включая сопоставление адресов и определение того, содержит ли span растровое изображение, такое как указатель, поэтому основная причина этого — более эффективное использование памяти: выделение, переработка и повторное использование.
преобразование размера
В дополнение к вышеупомянутым концепциям организации блоков памяти, есть несколько важных концепций размера. Мы должны поговорить о них. Не игнорируйте их важность. Они являются основой распределения памяти, организации и трансляции адресов.
-
object size: короткое имя в коде
size
, который относится к размеру объекта, применяемого для памяти. -
size class: короткое имя в коде
class
, который представляет собой уровень размера, что эквивалентно классификации размера в сегменте интервала определенного размера. Например, размер [1,8] принадлежит классу размера 1, а размер (8,16] принадлежит классу размера 2. . - span class: относится к уровню диапазона, но размер класса диапазона не пропорционален размеру диапазона. Класс span в основном используется для соответствия классу размера. Один класс размера соответствует двум классам span. Размер диапазона двух классов span одинаков, но функции разные. Один используется для хранения объектов, содержащих указатели, а другой используется для хранения объектов, которые не содержат указатели.Объект указателя, Span, который не содержит объект указателя, не нуждается в GC сканировании.
-
num of page: короткое имя в коде
npage
, представляет количество страниц, которое на самом деле является количеством страниц, содержащихся в диапазоне, который используется для выделения памяти.
Прежде чем вводить преобразование между этими размерами, мы должны взглянуть на приведенную ниже таблицу, которая определяет отношение сопоставления.
Верхние 2 строки добавляются мной вручную, первые 3 столбца — это класс размера, размер объекта и размер промежутка соответственно, а преобразование между размером, классом размера и количеством страниц выполняется в соответствии с этими 3 столбцами.
Кроме того, количество объектов в 4-м столбце представляет собой количество объектов, которые может вместить Span текущего уровня класса размера, а отходы хвоста в 5-м столбце — этоspan%obj
Результат вычисления, поскольку размер пролета не обязательно является целым числом, кратным размеру объекта. Последний столбец max waste представляет максимальный процент потраченной впустую памяти, рассчитанный вprintComment
В функции я не знаю, почему она рассчитывается таким образом.
Внимательно посмотрите на таблицу, а затем посмотрите вниз, чтобы увидеть, как реализовано преобразование.
На картинке преобразования размера памяти Go отмечено преобразование между различными размерами, которые являются массивами:class_to_size
,size_to_class*
а такжеclass_to_allocnpages
, содержимое этих трех массивов соответствует отношениям отображения в приведенной выше таблице. Напримерclass_to_size
, из приведенной выше таблицы размер сохраняемого объекта, соответствующего классу 1, равен 8, поэтомуclass_to_size[1]=8
, размер диапазона составляет 8192 байта, что составляет 8 КБ, что составляет 1 страницу, поэтомуclass_to_allocnpages[1]=1
.
Почему бы не использовать функции для вычисления различных преобразований, а вместо этого писать массивы?
Есть 1 очень важная причина:пространство для времени. Если вы внимательно наблюдаете, преобразования в приведенной выше таблице не могут быть преобразованы с помощью простых формул, например, соотношение между размером и классом размера не является пропорциональным. Эти цифры рассчитываются по более сложной формуле, которая приведена вmakesizeclass.go
Среди них есть экспоненциальные операции и циклы for, что приводит к временной сложности O(N*2^N) для каждого преобразования размера. Кроме того, для программы требуется много операций по обращению с памятью и управлению, и если ее нельзя выполнить быстро, то она очень неэффективна. Записав приведенное выше преобразование размера в массив, временная сложность преобразования размера напрямую снижается до O (1).
Перейти к распределению памяти
Используемые концепции были описаны, давайте посмотрим на принцип распределения памяти в Go.
Классификация памяти в Go не делится на малые, средние и большие объекты, как TCMalloc, но его малый объект подразделяется на крошечный объект, который относится к объектам размером от 1 до 16 байт и без указателей. Маленькие объекты и большие объекты разграничиваются только по размеру, никаких других различий не делается.
Маленькие объекты выделяются в mcache, а большие объекты выделяются непосредственно из mheap, из выделения памяти маленьких объектов.
Распределение малых объектов
преобразование размераВ этом разделе мы вводим таблицу преобразования, существует 66 классов размеров от 1 до 66, в коде_NumSizeClasses=67
Представляет фактическое количество используемых классов размера, то есть 67, от 0 до 67, класс размера 0 фактически не используется.
Как было сказано выше, одному размерному классу соответствуют два класса пролетов:
numSpanClasses = _NumSizeClasses * 2
numSpanClasses
Количество классов интервалов равно 134, поэтому нижний индекс класса интервала составляет от 0 до 133, поэтому класс интервала, отмеченный mcache на приведенном выше рисунке, имеет видspan class 0
прибытьspan class 133
. Каждый класс span указывает на диапазон, то есть mcache может иметь максимум 134 диапазона.
Найти интервалы для объектов
Процесс поиска пролетов выглядит следующим образом:
- Рассчитать размер памяти, необходимой объекту
- Рассчитайте требуемый размерный класс в соответствии с сопоставлением размерного класса.
- Вычислить класс span на основе класса размера и того, содержит ли объект указатель
- Получите диапазон, на который указывает этот класс диапазона.
Возьмем пример выделения объекта размером 24 байта, который не содержит указателя.
Согласно таблице сопоставления:
// class bytes/obj bytes/span objects tail waste max waste
// 1 8 8192 1024 0 87.50%
// 2 16 8192 512 0 43.75%
// 3 32 8192 256 0 46.88%
// 4 48 8192 170 32 31.52%
класс размера 3, его диапазон размеров объекта составляет (16,32] байт, 24 байта как раз в этом диапазоне, поэтому класс размера этого объекта равен 3.
Расчет класса Size для класса span выглядит следующим образом:
// noscan为true代表对象不包含指针
func makeSpanClass(sizeclass uint8, noscan bool) spanClass {
return spanClass(sizeclass<<1) | spanClass(bool2int(noscan))
}
Итак, соответствующий класс span:
span class = 3 << 1 | 1 = 7
Итак, объекту нужен диапазон, на который указывает класс span 7.
Выделить пространство объекта из диапазона
Диапазон можно разделить на множество частей в соответствии с размером объекта, который можно рассчитать из таблицы сопоставления.В качестве примера возьмите диапазон, соответствующий классу размера 3, размер диапазона составляет 8 КБ, а фактическое пространство, занимаемое каждый объект имеет размер 32 байта, этот диапазон делится на 256 блоков. Если имеется 256 блоков, адрес памяти каждого блока объекта может быть рассчитан в соответствии с начальным адресом диапазона.
При выделении памяти некоторые блоки памяти объекта в диапазоне заняты, а некоторые не заняты.Например, на приведенном выше рисунке все представляет собой один диапазон, синий блок представляет собой занятую память, а зеленый блок представляет собой незанятую Память.
При выделении памяти просто быстро найдите первый доступный зеленый блок, рассчитайте адрес памяти и при необходимости очистите данные блока памяти.
Как выделить объекты, когда в диапазоне нет места
Когда все блоки памяти в диапазоне заняты и не осталось места для дальнейшего выделения объектов, mcache подаст заявку на получение диапазона в mcentral, и mcache продолжит выделение объектов после получения диапазона.
mcentral предоставляет диапазон для mcache
Как и mcache, mcentral имеет 134 уровня классов спанов от 0 до 133, но каждый уровень сохраняет 2 списка спанов, то есть 2 связанных списка спанов:
-
nonempty
: Для отрезков в этом связанном списке все отрезки имеют хотя бы одно свободное пространство для объектов. Эти промежутки добавляются в связанный список, когда mcache освобождает промежутки. -
empty
: диапазон в этом связанном списке, все диапазоны не уверены, есть ли в нем свободное пространство для объектов. Когда диапазон будет передан mcache, он будет добавлен в пустой список.
Названия этих двух вещей всегда немного сбивали с толку.Рекомендуется прямо понимать пустое пространство как отсутствие объекта.
В реальном коде каждый класс span соответствует одному mcentral, а на рисунке все mcentral абстрагированы в единое целое.
Когда mcache запрашивает диапазон у mcentral, mcentral сначалаnonempty
Искать диапазон, удовлетворяющий условию, и если каждый найден, тоemtpy
Найдите спаны, соответствующие условиям, а затем передайте найденные спаны в mcache.
span управление mheap
2 дерева сохраняются в mheapбинарное отсортированное дерево, отсортированные по количеству страниц в диапазоне:
-
free
: спаны, хранящиеся в free, являются бесплатными спанами без сбора мусора. -
scav
: scav хранит незанятые диапазоны и спаны со сборкой мусора.
Если диапазон освобождается из-за сборки мусора, этот диапазон будет добавлен кscav
, в противном случае добавляется кfree
, например, память, только что запрошенная у ОС, также состоит из Span.
В mheap также есть арены, которые состоят из группы heapArenas, и каждая heapArena содержит последовательныеpagesPerArena
Span, это в основном для управления спанами и службами сборки мусора для mheap.
Сама mheap — глобальная переменная, и данные в ней — это тоже память, напрямую запрошенная из ОС, а не в той части памяти, которой управляет mheap.
mcentral запрашивает у mheap диапазон
Когда mcentral предоставляет диапазон для mcache, еслиemtpy
Нет подходящих спанов, и mcentral будет применять спаны из mheap.
mcentral должен предоставить mheap необходимое количество страниц памяти и уровень класса span, а затем он сначала начинает сfree
Поиск доступных пролетов вscav
Ищите доступный диапазон в середине, если он не найден, он будет обращаться к ОС за памятью, а затем повторно искать 2 дерева, он обязательно найдет диапазон. Если найденный пролет больше требуемого, то пролет разбивается на 2 пролета, один из которых как раз нужного размера, а оставшиеся пролеты добавляются кfree
Перейдите к середине, а затем задайте основную информацию о необходимом пролете, а затем передайте ее mcentral.
mheap запрашивает память у ОС
Когда mheap не хватает памяти, mheap обращается к ОС за памятью, сохраняет запрошенную страницу памяти в диапазон, а затем вставляет диапазон в диапазон.free
Дерево .
В 32-битной системе mheap зарезервирует часть пространства.Когда в mheap нет места, сначала подать заявку на него из зарезервированного пространства.Если зарезервированное пространство памяти также пропало, обратиться к ОС.
Размещение крупных объектов
Выделение больших объектов намного проще, чем мелких.Процесс на 99% такой же, как и обращение mcentral к памяти из mheap, поэтому он не будет повторяться.Разница в том, что mheap будет записывать некоторую статистику о больших объектах. Видетьmheap.alloc_m()
.
Перейти к сбору мусора и освобождению памяти
Если вы только выделяете и выделяете память, память в конечном итоге будет исчерпана Go использует сборку мусора для сбора спанов, которые больше не используются.mspan.scavenge()
Отпустите спан в ОС (не то чтобы отпустите, просто скажите ОС, что информация об этом куске памяти бесполезна, если она вам нужна, заберите его обратно), затем передайте его в mheap, mheap сливает спаны по спанам , и объединяет объединенные диапазоны вscav
В дереве при ожидании перераспределения памяти перераспределение памяти выполняется по mheap.Go сборка мусора тоже сильная тема.Планируется написать отдельную статью позже.
Теперь давайте сосредоточимся на том, как программа Go освобождает память для операционной системы?
Функция освобождения памятиsysUnused
, это будетmspan.scavenge()
передача:
// MAC下的实现
func sysUnused(v unsafe.Pointer, n uintptr) {
// MADV_FREE_REUSABLE is like MADV_FREE except it also propagates
// accounting information about the process to task_info.
madvise(v, n, _MADV_FREE_REUSABLE)
}
в записке говорится_MADV_FREE_REUSABLE
а такжеMADV_FREE
Функция аналогична, ее функция состоит в том, чтобы предоставить ядру предложение:Память в этом диапазоне адресов памяти больше не используется и может быть переработана. А вот перезаряжается ли ядро, и когда перезаряжается, это уже дело ядра. Если ядро действительно освобождает этот кусок памяти, когда программа Go снова использует этот адрес, ядро переназначит виртуальный адрес на физический адрес. Таким образом, в случае достаточного объема памяти ядру не нужно немедленно освобождать память.
4. Используйте стек памяти
Наконец, упомянем память стека. С точки зрения макросов управление памятью должно иметь не только кучи, но и стеки.
Каждая горутина имеет свой собственный стек. Начальный размер стека 2 КБ. Один миллион горутин будет занимать 2 ГБ, но стек горутин будет автоматически расширяться, когда 2 КБ недостаточно. Когда стек расширится до 4 КБ, будет занимать один миллион горутин 4ГБ.
Есть очень хорошая статья об управлении памятью стека goroutine, колонка от Ele.me Framework Technology Department: "Разговор о стеке горутин», отрывок из содержимого внутри, вы можете почувствовать это:
Вы можете видеть это в вызове rpc (grpc invoke), стек расширится (runtime.morestack), что означает, что любой вызов rpc в подпрограмме чтения и записи вызовет расширение стека, занятое пространство памяти будет удвоено, стек 4 КБ станет 8 КБ, а объем памяти соединения 100 Вт увеличится с 8 ГБ для 16 ГБ. (полный дуплекс, не считая других накладных расходов), это кошмар.
Кроме того, я рекомендую ознакомительную статью о компиляции, переведенную Цао Да, в которой также рассказывается о расширении стека:Глава 1: Начало работы с Go Assembly, КстатиначинаяСобрать.
5. Резюме
Принцип распределения памяти не будет повторно рассматриваться, а будет подчеркнут две важные идеи:
- Используйте кеш для повышения эффективности. Идею кеша можно увидеть везде во всей системе хранения.Распределение и управление памятью в Go также используют кеш.Использование кеша уменьшает количество системных вызовов, а второе -уменьшает гранулярность блокировок и количество замков.Из этих двух точек, чтобы повысить эффективность управления памятью.
- Обмен места на время для повышения эффективности управления памятью. Пространство во времени – это часто используемая идея оптимизации производительности. Эта идея на самом деле очень распространена. Например, суть структур данных, таких как хэш, карта и двоичное дерево сортировки, — пространство во времени, что также очень распространено. в базах данных, таких как индексы и индексы базы данных, кэш представлений и данных и т. д., а также базы данных кеша, такие как Redis, также являются идеей изменения пространства во времени.
6. Ссылки
Помимо статей, уже рекомендованных в статье, вот еще несколько статей, которые стоит прочитать:
- Статья Quancheng о распределении памяти очень полезна:nuggets.capable/post/684490…
- Необычно подробная статья по анализу исходного кода, после прочтения которой не хочется писать статью по анализу исходного кода:Блог Woo Woo.cn на.com/ZK Web/Fear/788…
- Немного интересна статья о железе:woohoo.info Q.способен на /article/IE и…
- Общая блок-схема из этого поста великолепна:СМИ. А как обо мне, sec.com/blog/malloc…
7. Пасхальные яйца
При поиске информации во многих статьях упоминалась книга "The Linux Programming Interface", о кэше потоков, если вам интересно прочитать главу 31 этой книги.