Язык Go имеет встроенную среду выполнения (то есть среду выполнения), которая отказывается от традиционного метода выделения памяти и заменяет его самоуправлением. Это позволяет автономным образом реализовать лучшие модели использования памяти, такие как объединение памяти, предварительное выделение и т. д. Таким образом, системные вызовы не требуются для каждого выделения памяти.
Алгоритм выделения памяти среды выполнения Golang в основном основан на языке C, разработанном Google.TCMalloc算法
, полное имяThread-Caching Malloc
. Основная идея состоит в том, чтобы разделить память на многоуровневое управление, тем самым уменьшив степень детализации блокировки. Он управляет доступной памятью кучи с помощью двухуровневого распределения: каждый поток поддерживает независимый пул памяти и сначала выделяет память из этого пула памяти, а затем передает ее в глобальный пул памяти только тогда, когда пула памяти недостаточно. глобального пула памяти разными потоками.
Базовые концепты
Когда Go запускает программу, она сначала обращается к операционной системе за частью памяти (обратите внимание, что в настоящее время это только виртуальное адресное пространство и фактически не выделяет память), а затем управляет ею самостоятельно после ее вырезания. на мелкие кусочки.
Запрошенный блок памяти распределяется по трем областям: 512 МБ, 16 ГБ и 512 ГБ соответственно на X64.
arena区域
Это то, что мы называем областью кучи. Память, динамически выделяемая Go, находится в этой области.8KB
размер страниц, некоторые страницы вместе называютсяmspan
.
bitmap区域
логотипarena
какие адреса в области сохраняют объект, и используют4bit
Бит флага указывает, содержит ли объект указатель,GC
Информация о теге.bitmap
один изbyte
объем памятиarena
4 размера указателя (размер указателя 8B) памяти в регионе, поэтомуbitmap
Размер участка512GB/(4*8B)=16GB
.
Из приведенного выше рисунка видно, что часть битовой карты с высоким адресом указывает на часть области арены с низким адресом, то есть адрес битовой карты увеличивается от старшего адреса к младшему адресу.
spans区域
хранитьmspan
(то есть некоторыеarena
Базовая единица управления памятью объединена разделенными страницами, о которых речь пойдет позже) указателями, каждый указатель соответствует странице, поэтомуspans
Размер участка512GB/8KB*8B=512MB
. Разделить на 8 КБ - это расчетarena
Количество страниц в этом районе, и конечное умножение на 8 - это расчетspans
Размер всех указателей на регион. Создайтеmspan
, заполнить соответствующую страницу за страницейspans
площадь, в переработкеobject
, легко найтиmspan
.
блок управления памятью
mspan
: базовая единица управления памятью в Go, состоящая из непрерывного8KB
Большой блок памяти, состоящий из страниц. Обратите внимание, что страница здесь не совпадает со страницей самой операционной системы, она обычно в несколько раз больше страницы операционной системы. Вкратце в одном предложении:mspan
это файл, содержащий начальный адрес,mspan
Двусвязный список спецификаций, количество страниц и т.д.
каждыйmspan
по своим свойствамSize Class
размер делится на несколькоobject
, каждыйobject
Объект можно сохранить. и будет использовать растровое изображение, чтобы отметить его неиспользуемыйobject
. АтрибутыSize Class
Принять решениеobject
размер, при этомmspan
будет присвоено только иobject
Предметы одинакового размера, естественно, предметы меньшего размераobject
размер. Есть еще одна концепция:Span Class
, это иSize Class
означает почти то же самое,
Size_Class = Span_Class / 2
Это потому, что фактически каждыйSize Class
есть дваmspan
, то есть есть дваSpan Class
. Один из них присваивается объекту, содержащему указатель, а другой присваивается объекту, не содержащему указателя. Это принесет пользу механизму сборки мусора, о котором будет рассказано в следующей статье.
Как показано ниже,mspan
Состоит из набора смежных страниц, разделенных наobject
.
В Go1.9.2mspan
изSize Class
Всего 67 видов, каждыйmspan
Размер разделяемого объекта кратен 8*2n, что жестко прописано в коде:
// path: /usr/local/go/src/runtime/sizeclasses.go
const _NumSizeClasses = 67
var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536,1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}
в соответствии сmspan
изSize Class
можно разделить наobject
размер. НапримерSize Class
равно 3,object
Размер 32В. Объект размером 32 байта может хранить объекты размером от 17 до 32 байт. Для крошечных объектов (менее 16 байт) аллокатор объединит их и назначит несколько объектов одному и тому же.object
середина.
Наибольшее число в массиве 32768, что составляет 32 КБ. Если оно превышает этот размер, это большой объект. Он будет обработан специальным образом, который будет введен позже. Кстати, типаSize Class
0 означает большие объекты, которые фактически выделяются непосредственно памятью кучи, в то время как маленькие объекты должны проходить черезmspan
выделить.
Для mspan этоSize Class
Он определит количество страниц, которые он может получить, что также жестко запрограммировано в коде:
// path: /usr/local/go/src/runtime/sizeclasses.go
const _NumSizeClasses = 67
var class_to_allocnpages = [_NumSizeClasses]uint8{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 2, 3, 1, 3, 2, 3, 4, 5, 6, 1, 7, 6, 5, 4, 3, 5, 7, 2, 9, 7, 5, 8, 3, 10, 7, 4}
Например, когда мы хотим подать заявку наobject
размер32B
изmspan
, соответствующий индекс в class_to_size равен 3, а индекс 3 находится вclass_to_allocnpages
Соответствующий номер страницы в массиве равен 1.
mspan
Определение структуры:
// path: /usr/local/go/src/runtime/mheap.go
type mspan struct {
//链表前向指针,用于将span链接起来
next *mspan
//链表前向指针,用于将span链接起来
prev *mspan
// 起始地址,也即所管理页的地址
startAddr uintptr
// 管理的页数
npages uintptr
// 块个数,表示有多少个块可供分配
nelems uintptr
//分配位图,每一位代表一个块是否已分配
allocBits *gcBits
// 已分配块的个数
allocCount uint16
// class表中的class ID,和Size Classs相关
spanclass spanClass
// class表中的对象大小,也即块大小
elemsize uintptr
}
мы будемmspan
С более широкой точки зрения:
На картинке выше вы можете видеть, что их два.S
указывая на то жеmspan
, потому что дваS
заостренныйP
принадлежат к одному и тому жеmspan
из. Итак, поarena
Адрес на может быстро найти адрес, который указывает на негоS
,пройти черезS
могу найтиmspan
, вспомним, что мы говорили ранееmspan
Каждый указатель региона соответствует странице.
Предположим, что самый левый первыйmspan
изSize Class
равно 10, согласно предыдущемуclass_to_size
массив, что дает этоmsapn
сегментированныйobject
Размер составляет 144 байта, а количество объектов, которые можно выделить, рассчитывается как8KB/144B=56.89
, округляем до 56, так что будет потеря памяти, в исходном коде Go есть всеSize Class
изmspan
размер потраченной впустую памяти; затем на основеclass_to_allocnpages
массив, получить этоmspan
только на 1page
состав; предположим, чтоmspan
присваивается объекту без указателя, тоspanClass
равняется 20.
startAddr
указать прямо наarena
место в области, которая представляет этоmspan
начальный адрес ,allocBits
Указывает на растровое изображение, где каждый бит представляет, выделяется ли блок как объект;allocCount
Указывает общее количество выделенных объектов.
Итак, первый слеваmspan
Параметры каждого поля показаны на следующем рисунке:
компоненты управления памятью
Выделение памяти осуществляется распределителем памяти. Дозатор состоит из 3-х компонентов:mcache
, mcentral
, mheap
.
mcache
mcache
: каждый рабочий поток привязан к mcache, доступен локальный кешmspan
Ресурсы, это может быть назначено непосредственно горутине, потому что нет необходимости в нескольких соревнованиях горутины, поэтому она не будет потреблять ресурсы блокировки.
mcache
Структурное определение:
//path: /usr/local/go/src/runtime/mcache.go
type mcache struct {
alloc [numSpanClasses]*mspan
}
numSpanClasses = _NumSizeClasses << 1
mcache
использоватьSpan Classes
Управляет несколькими распределениями как индексомmspan
, который содержит все спецификацииmspan
. это_NumSizeClasses
2 раза, то есть67*2=134
Почему существует удвоение отношений, прежде чем мы упоминали: Чтобы ускорить скорость восстановления памяти, половина массивовmspan
Объекты, размещенные в , не содержат указателей, а другая половина содержит указатели.
для объектов без указателяmspan
Нет необходимости дополнительно сканировать его на наличие ссылок на другие активные объекты во время сборки мусора. Я расскажу об этом позже в статье о сборке мусора, но в этот раз сначала пойду сюда.
mcache
Во время инициализации нетmspan
ресурс, он будет динамически меняться отmcentral
Приложение будет кэшировано позже. Если размер объекта меньше или равен 32 КБ, используйтеmcache
соответствующих спецификацийmspan
выделить.
mcentral
mcentral
: для всехmcache
предоставить нарезанныйmspan
ресурс. каждыйcentral
сохранить глобал определенного размераmspan
Список, как назначенных, так и неназначенных. каждыйmcentral
соответствующийmspan
,иmspan
вид, который заставляет его делитьсяobject
Различные размеры. когда рабочий потокmcache
Нет подходящего (то есть определенного размера) вmspan
будет изmcentral
Получать.
mcentral
Он используется всеми рабочими потоками, и существует конкуренция между несколькими горутинами, поэтому ресурсы блокировки будут потребляться. Определение структуры:
//path: /usr/local/go/src/runtime/mcentral.go
type mcentral struct {
// 互斥锁
lock mutex
// 规格
sizeclass int32
// 尚有空闲object的mspan链表
nonempty mSpanList
// 没有空闲object的mspan链表,或者是已被mcache取走的msapn链表
empty mSpanList
// 已累计分配的对象个数
nmalloc uint64
}
empty
Указывает, что в этом спискеmspan
были выделеныobject
, или былcache
забралиmspan
,этоmspan
Он используется исключительно этим рабочим потоком. иnonempty
указывает, что есть свободные объектыmspan
список. каждыйcentral
структура находится вmheap
в обеспечении.
просто скажиmcache
отmcentral
приобретение и возвратmspan
процесс:
-
Получать замок; от
nonempty
связанный список, чтобы найти доступныйmspan
; и измените его сnonempty
связанный список удалить; будет удаленmspan
принять участие вempty
связанный список;mspan
Вернуться к рабочему потоку; разблокировать. -
возвращение замок; замок
mspan
отempty
удалить связанный список;mspan
принять участие вnonempty
связанный список, разблокирован.
mheap
mheap
: представляет все пространство кучи, занимаемое программой Go, программа Go используетmheap
глобальный объект_mheap
для управления динамической памятью.
когдаmcentral
нет свободного времениmspan
, будетmheap
Применять. иmheap
При отсутствии ресурсов у операционной системы запрашивается новая память.mheap
В основном используется для выделения памяти больших объектов и управления неразрезаннымиmspan
, для дачиmcentral
Разрезать на мелкие предметы.
В то же время мы также видим, чтоmheap
содержит все характеристикиmcentral
, поэтому, когдаmcache
отmcentral
Применятьmspan
, только в независимомmcentral
Использование блокировок в приложении не повлияет на применение других спецификаций.mspan
.
mheap
Определение структуры:
//path: /usr/local/go/src/runtime/mheap.go
type mheap struct {
lock mutex
// spans: 指向mspans区域,用于映射mspan和page的关系
spans []*mspan
// 指向bitmap首地址,bitmap是从高地址向低地址增长的
bitmap uintptr
// 指示arena区首地址
arena_start uintptr
// 指示arena区已使用地址位置
arena_used uintptr
// 指示arena区末地址
arena_end uintptr
central [67*2]struct {
mcentral mcentral
pad [sys.CacheLineSize - unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte
}
}
На приведенном выше рисунке мы видим, что bitmap и arena_start указывают на один и тот же адрес, потому что адрес bitmap увеличивается от старшего к младшему, поэтому они указывают на одну и ту же ячейку памяти.
Процесс распределения
предыдущий пост«Где переменные в Голанге?»Мы упоминали, что размещение переменной в стеке или в куче определяется результатом анализа побега. В нормальных условиях компиляторы склонны размещать переменные в стеке из-за его небольших накладных расходов, наиболее экстремальным является «нулевой мусор», все переменные будут размещены в стеке, так что не будет фрагментации памяти, повторного использования мусора или чего-то в этом роде. тот.
Когда распределитель памяти Go выделяет объекты, он делится на три категории в зависимости от размера объекта: маленькие объекты (меньше или равные 16 байт), обычные объекты (больше 16 байт, меньше или равные 32 КБ) и большие объекты. (более 32 КБ).
Общий процесс раздачи:
-
объекты размером 32 КБ, выделенные непосредственно из mheap;
- Объекты
- (16B, 32KB] объект, сначала рассчитайте размер объекта, а затем используйте mspan соответствующего размера в mcache для выделения;
- Если mcache не имеет mspan соответствующего размера, обратитесь к mcentral
- Если у mcentral нет mspan соответствующего размера, применить к mheap
- Если в mheap нет подходящего размера mspan, обратитесь к операционной системе
Суммировать
Выделение памяти в языке Go очень сложно, и один из его принципов заключается в том, что память должна использоваться повторно, если ее можно использовать повторно. За исходным кодом сложно следить, и позже может быть другая статья о чтении исходного кода и распределении памяти. Кратко подведем итоги этой статьи.
В статье распределение памяти в Go рассматривается с приблизительной точки зрения, не вдаваясь в подробности. Вообще говоря, в этой мере достаточно понять его принцип.
- Когда программа запускается, Go обращается к операционной системе за большим куском памяти, а затем управляет ею самостоятельно.
- Базовой единицей управления памятью Go является mspan, который состоит из нескольких страниц, и каждый mspan может выделять объект определенного размера.
- mcache, mcentral и mheap — это три основных компонента управления памятью Go, которые являются прогрессивными. mcache управляет mspan, кэшированным локально потоками; mcentral управляет глобальным mspan для использования всеми потоками; mheap управляет всей динамически выделяемой памятью в Go.
- Очень маленькие объекты будут выделены в объекте для экономии ресурсов и использования крошечного распределителя для выделения памяти; обычно маленькие объекты выделяют память через mspan; большие объекты выделяют память непосредственно через mheap.
использованная литература
[Легко понять, очень понятно] https://yq.aliyun.com/articles/652551
[Процесс инициализации распределителя памяти, блок-схема выделения очень подробная] https://www.jianshu.com/p/47691d870756
[Глобальная карта] https://swanspouse.github.io/2018/08/22/golang-memory-model/
[Чтение исходного кода Rain Mark Go1.5] https://github.com/qyuhen/book
[Картинка хорошая] https://www.jianshu.com/p/47691d870756
[Общий смысл] https://juejin.cn/post/6844903506948669447
[Интерпретация исходного кода] http://legendtkl.com/2017/04/02/golang-alloc/
[Ключевая рекомендация уходит вглубь транзистора, картинка очень хорошая] https://www.linuxzen.com/go-memory-allocator-visual-guide.html
[Общее описание процесса выделения объектов] http://gocode.cc/project/4/article/103
[Актуальная команда Linux] https://mikespook.com/2014/12/%E7%90%86%E8%A7%A3-go-%E8%AF%AD%E8%A8%80%E7%9A%84 %E5%86%85%E5%AD%98%E4%BD%BF%E7%94%A8/
[Общая блок-схема Ссылка на вызов функции выделения объектов] http://blog.newbmiao.com/2018/08/20/go-source-analysis-of-memory-alloc.html
[Объяснение исходного кода очень подробное] https://www.cnblogs.com/zkweb/p/7880099.html
[Чтение исходного кода] https://zhuanlan.zhihu.com/p/34930748