Графическое выделение памяти Go

Go

Язык Go имеет встроенную среду выполнения (то есть среду выполнения), которая отказывается от традиционного метода выделения памяти и заменяет его самоуправлением. Это позволяет автономным образом реализовать лучшие модели использования памяти, такие как объединение памяти, предварительное выделение и т. д. Таким образом, системные вызовы не требуются для каждого выделения памяти.

Алгоритм выделения памяти среды выполнения Golang в основном основан на языке C, разработанном Google.TCMalloc算法, полное имяThread-Caching Malloc. Основная идея состоит в том, чтобы разделить память на многоуровневое управление, тем самым уменьшив степень детализации блокировки. Он управляет доступной памятью кучи с помощью двухуровневого распределения: каждый поток поддерживает независимый пул памяти и сначала выделяет память из этого пула памяти, а затем передает ее в глобальный пул памяти только тогда, когда пула памяти недостаточно. глобального пула памяти разными потоками.

Базовые концепты

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

Запрошенный блок памяти распределяется по трем областям: 512 МБ, 16 ГБ и 512 ГБ соответственно на X64.

堆区总览

arena区域Это то, что мы называем областью кучи. Память, динамически выделяемая Go, находится в этой области.8KBразмер страниц, некоторые страницы вместе называютсяmspan.

bitmap区域логотипarenaкакие адреса в области сохраняют объект, и используют4bitБит флага указывает, содержит ли объект указатель,GCИнформация о теге.bitmapодин изbyteобъем памятиarena4 размера указателя (размер указателя 8B) памяти в регионе, поэтомуbitmapРазмер участка512GB/(4*8B)=16GB.

bitmap arena

bitmap arena

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

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.

page mspan

В 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 Class0 означает большие объекты, которые фактически выделяются непосредственно памятью кучи, в то время как маленькие объекты должны проходить через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С более широкой точки зрения:

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Параметры каждого поля показаны на следующем рисунке:

左起第一个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. это_NumSizeClasses2 раза, то есть67*2=134Почему существует удвоение отношений, прежде чем мы упоминали: Чтобы ускорить скорость восстановления памяти, половина массивовmspanОбъекты, размещенные в , не содержат указателей, а другая половина содержит указатели.

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

mcache

mcacheВо время инициализации нетmspanресурс, он будет динамически меняться отmcentralПриложение будет кэшировано позже. Если размер объекта меньше или равен 32 КБ, используйтеmcacheсоответствующих спецификацийmspanвыделить.

mcentral

mcentral: для всехmcacheпредоставить нарезанныйmspanресурс. каждыйcentralсохранить глобал определенного размераmspanСписок, как назначенных, так и неназначенных. каждыйmcentralсоответствующийmspanmspanвид, который заставляет его делиться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 
}

mcentral

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
	}
}

mheap

На приведенном выше рисунке мы видим, что 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.

QR

использованная литература

[Легко понять, очень понятно] 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