Позвольте вам оценить очарование исходного кода Go ---- Подробное объяснение принципа памяти Go

Go

1. Раздел памяти

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

В Windows программа представляет собой обычный исполняемый файл.Ниже перечислены основные условия двоичного исполняемого файла:

Как видно из приведенного выше рисунка, перед тем, как программа не будет запущена, то есть до того, как программа будет загружена в память, исполняемая программа была разделена на три части информации, а именно:Область кода (текст),** область данных (данные)иОбласть неинициализированных данных (bss)**3 раздела.

Некоторые люди напрямую называют данные и bss вместестатическая областьилиглобальная область.

1.1 Область кода (текст)

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

1.2 Область глобальных данных инициализации/область статических данных (данные)

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

1, 3 Область неинициализированных данных (bss)

Сохраняются глобальные неинициализированные переменные и неинициализированные статические переменные. Данные в области неинициализированных данных инициализируются ядром до 0 или пустыми (nil) перед началом выполнения программы.

До загрузки программы в память размер кодовой области и глобальной области (data и bss) фиксирован и не может быть изменен во время работы программы.

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

1, 4 области стека (стек)

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

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

1, 5 область кучи (куча)

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

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

Язык Go, Java, Python и т. д. имеют механизм сборки мусора (GC) для автоматического освобождения памяти.

2. Перейти к распределению памяти во время выполнения

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

Алгоритм выделения памяти среды выполнения Golang в основном основан на языке C, разработанном Google.Алгоритм TCMalloc, полное имяThread-Caching Malloc.

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

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

2.1 Базовая стратегия

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

**Примечание.** Распределитель памяти управляет только блоками памяти, не заботится о состоянии объекта и не будет активно перерабатывать. После того, как механизм сборки мусора завершит операцию очистки, он запускает операцию перезаписи памяти.

2.2 Блок управления памятью

Распределитель делит блоки памяти, которыми он управляет, на два типа:

  • span: большой блок памяти, состоящий из нескольких смежных страниц (страница [размер: 8 КБ]).
  • объект: разделите диапазон на несколько небольших частей в соответствии с определенным размером, и каждая маленькая часть может хранить объект.

использовать:

пространство для внутреннего управления

объект объектно-ориентированное присваивание

//path:Go SDK/src/runtime/malloc.go

_PageShift      = 13
_PageSize = 1 << _PageShift		//8KB

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

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

**Примечание.** В настоящее время это просто виртуальное адресное пространство, и память на самом деле не выделяется.

  • площадь арены

    Это так называемая область кучи, в которой находится память, динамически выделяемая Go, она делит память на страницы по 8 КБ, а некоторые страницы объединяются и называются mspan.

    //path:Go SDK/src/runtime/mheap.go
    
    type mspan struct {
    	next           *mspan    	// 双向链表中 指向下一个
    	prev           *mspan    	// 双向链表中 指向前一个
    	startAddr      uintptr   	// 起始序号
    	npages         uintptr   	// 管理的页数
    	manualFreeList gclinkptr 	// 待分配的 object 链表
         nelems 		   uintptr 		// 块个数,表示有多少个块可供分配
         allocCount     uint16		// 已分配块的个数
    	...
    }
    
  • растровая область

    Определите, какие адреса в области арены сохраняют объект, и используйте 4-битный флаг, чтобы указать, содержит ли объект указатель и информацию о маркировке GC.

  • охватывает площадь

    Сохраните указатели mspan, каждый указатель соответствует странице, поэтому размер области промежутков составляет 512 ГБ/8 КБ * 8B = 512 МБ.

    Разделите на 8 КБ, чтобы вычислить количество страниц в области арены, и, наконец, умножьте на 8, чтобы вычислить размер всех указателей в области промежутков.

2.3 Компоненты управления памятью

Выделение памяти осуществляется распределителем памяти. Дозатор состоит из 3-х компонентов:

  • cache

    Каждый рабочий поток среды выполнения привязан к кешу для выделения незаблокированных объектов.

  • central

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

  • heap

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

2, 3, 1 кеш

cache: каждый рабочий поток привязан к mcache, который локально кэширует доступные ресурсы mspan.

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

Определение структуры mcache:

//path:Go SDK/src/runtime/mcache.go

_NumSizeClasses = 67					//67
numSpanClasses = _NumSizeClasses << 1	//134

type mcache struct {
	alloc [numSpanClasses]*mspan		//以numSpanClasses 为索引管理多个用于分配的 span
}

mcache использует Span Classes в качестве индекса для управления несколькими mspan для распределения, которые содержат mspan всех спецификаций.

Это 2 раза _NumSizeClasses , что составляет 67 * 2 = 134, почему существует двойное отношение.

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

2, 3, 2 центральные

Central: предоставляет сегментированные ресурсы mspan для всех mcache.

Каждый центр поддерживает глобальный список mspan определенного размера, как выделенный, так и нераспределенный.

Каждый mcentral соответствует типу mspan, а тип mspan приводит к тому, что размеры разделяемых им объектов различаются.

//path:Go SDK/src/runtime/mcentral.go

type mcentral struct {
	lock      mutex     	// 互斥锁
	sizeclass int32     	// 规格
	nonempty  mSpanList 	// 尚有空闲object的mspan链表
	empty     mSpanList 	// 没有空闲object的mspan链表,或者是已被mcache取走的msapn链表
	nmalloc   uint64    	// 已累计分配的对象个数
}

2, 3, 3 кучи

куча: представляет все пространство кучи, занимаемое программой Go.Программа Go использует глобальный объект mheap _mheap для управления памятью кучи.

Когда у mcentral нет свободного mspan, он будет применяться к mheap. Когда у mheap нет ресурсов, она обращается к операционной системе за новой памятью. mheap в основном используется для выделения памяти для больших объектов и для управления неразрезанным mspan, который используется для разрезания mcentral на мелкие объекты.

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

//path:Go SDK/src/runtime/mheap.go
type mheap struct {
	lock        mutex
	spans       []*mspan // spans: 指向mspans区域,用于映射mspan和page的关系
	bitmap      uintptr  // 指向bitmap首地址,bitmap是从高地址向低地址增长的
	arena_start uintptr  // 指示arena区首地址
	arena_used  uintptr  // 指示arena区已使用地址位置
	arena_end   uintptr  // 指示arena区末地址
	central [numSpanClasses]struct {
		mcentral mcentral
		pad      [sys.CacheLineSize-unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte
	}					//每个 central 对应一种 sizeclass
}

2.4 Процесс распространения

  • Рассчитать размер выделяемого объекта (size_class)
  • Найдите интервалы с одинаковыми характеристиками из массива cache.alloc.
  • Извлечение доступных объектов из связанного списка span.manualFreeList
  • Если span.manualFreeList пуст, получите новый диапазон из центрального
  • Если Central.nonempty пуст, возьмите его из heap.free/freelarge и разделите на связанный список объектов.
  • Если в куче нет диапазона подходящего размера, запросите новую память у операционной системы.

2.5 Процесс выпуска

  • Вернуть объект, помеченный как пригодный для повторного использования, в список span.freelist, к которому он принадлежит.
  • Диапазон возвращается к центральному, что может обеспечить повторную выборку кеша.
  • Если span восстанавливает все объекты, верните их в кучу для повторного нарезки и повторного использования.
  • Регулярно сканировать незанятые спаны в куче и освобождать занимаемую ими память

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

2.6 Резюме

Выделение памяти в языке Go очень сложно, и один из его принципов заключается в том, что память должна использоваться повторно, если ее можно использовать повторно.

  • Когда программа запускается, Go обращается к операционной системе за большим куском памяти, а затем управляет ею самостоятельно.
  • Базовой единицей управления памятью Go является mspan, который состоит из нескольких страниц, и каждый mspan может выделять объект определенного размера.
  • mcache, mcentral и mheap — это три основных компонента управления памятью Go, которые являются прогрессивными. mcache управляет mspan, кэшированным локально потоками; mcentral управляет глобальным mspan для использования всеми потоками; mheap управляет всей динамически выделяемой памятью в Go.
  • Как правило, для небольших объектов память выделяется с помощью mspan, для больших объектов память выделяется непосредственно с помощью mheap.

Далее большое черное пятно в языке Go: сборка мусора (GC). Вы можете обратить внимание на наш открытый класс, Мастер проведет вас, чтобы понять разработку и механизм GC на языке Go, отсканировать QR-код и посмотреть прямую трансляцию открытого класса.