Глубокое понимание деталей памяти языка GO

Go

Введение

Глубокое изучение golang, необходимо понять этот фрагмент памяти, тщательно изучить следующий фрагмент памяти, включая распределение памяти, модель памяти, анализ побега. Давайте иметь возможность обратить внимание на следующий кусок программирования.

Во-вторых, выделение памяти

(1) Сначала разберитесь с четырьмя связанными структурами данных здесь

1. мспан

Через next и prev формируется двусвязный список, а mspan отвечает за управление адресным пространством N страниц, начиная с startAddr. является базовой единицей распределения памяти. Это основная единица управления памятью.

//保留重要成员变量
type mspan struct {
	next *mspan     // 链表中下个span
	prev *mspan     // 链表中上个span	startAddr uintptr // 该mspan的起始地址
	freeindex uintptr // 表示分配到第几个块
	npages    uintptr // 一个span中含有几页
	sweepgen    uint32 // GC相关
	incache     bool       // 是否被mcache占用
	spanclass   spanClass  // 0 ~ _NumSizeClasses之间的一个值,比如,为3,那么这个mspan被分割成32byte的块
}

2. МакЭш

В go каждому P будет выделен mcache, который является частным, и для выделения памяти отсюда не требуется блокировка.

type mcache struct {
	tiny             uintptr // 小对象分配器
	tinyoffset       uintptr // 小对象分配偏移
	local_tinyallocs uintptr // number of tiny allocs not counted in other stats
	alloc [numSpanClasses]*mspan // 存储不同级别的mspan
}

3, МЦЕНТРАЛЬНЫЙ

Когда mcache недостаточно, он запросит память у mcentral. Структура на самом деле находится в mheap, поэтому мне кажется, что это действует как мост.

type mcentral struct {
	lock      mutex    // 多个P会访问,需要加锁
	spanclass spanClass  // 对应了mspan中的spanclass
	nonempty  mSpanList // 该mcentral可用的mspan列表
	empty     mSpanList // 该mcentral中已经被使用的mspan列表
}

4. м куча

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

type mheap struct {
	lock      mutex                    // 是公有的,需要加锁
	free      [_MaxMHeapList]mSpanList // 未分配的spanlist,比如free[3]是由包含3个 page 的 mspan 组成的链表	
	freelarge mTreap                   // mspan组成的链表,每个mspan的 page 个数大于_MaxMHeapList
	busy      [_MaxMHeapList]mSpanList // busy lists of large spans of given length
	busylarge mSpanList                // busy lists of large spans length >= _MaxMHeapList
	allspans []*mspan                  // 所有申请过的 mspan 都会记录在 allspans
	spans []*mspan                     // 记录 arena 区域页号(page number)和 mspan 的映射关系

	arena_start uintptr // arena是Golang中用于分配内存的连续虚拟地址区域,这是该区域开始的指针
	arena_used  uintptr // 已经使用的内存的指针
	arena_alloc uintptr
	arena_end   uintptr

	central [numSpanClasses]struct {
		mcentral mcentral
		pad      [sys.CacheLineSize - unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte //避免伪共享(false sharing)问题
	}
	
	spanalloc             fixalloc // allocator for span*
	cachealloc            fixalloc // mcache分配器

}

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

(2) Сведения о распределении памяти

Исходный код здесь не раскрывается, просто знайте правила распространения. (В golang1.10, MacOs 10.12 следующие 32 КБ заменены на 64 КБ)

1, размер объекта > 32 КБ; mheap используется с прямым назначением.

2, размер объекта

3. Если размер объекта > 16 байт && размер

В-третьих, модель памяти

Вот что происходит до (на голанге)Предположим, что A и B представляют собой две операции, выполняемые многопоточной программой. Если A происходит до B, то влияние операции A на память будет видно потоку, выполняющему B (и до выполнения B).)

(1) Функция инициализации

1. Пакет P2 импортируется в P1, затем все операции в функции инициализации Происходит до P1 в P2

2, все функции инициализации выполняются до основной функции

(2) Channel

1, операция приема, соответствующая операции отправки, происходит перед элементом

2. Операция закрытия на канале Происходит до получения операции уведомления о закрытии

3. Для канала без кэша операция получения элемента выполняется при отправке, соответствующей Happens Before, чтобы завершить операцию.

4. Для канала с буфером предположим, что буфер канала Размер равен C, тогда для операции приема k-го элемента k+C-я отправка происходит до завершения операции. .

В-четвертых, анализ побега

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

(1) Что такое побег из памяти

Проще говоря, объекты, которым должна быть выделена память в стеке, уходят в кучу для выделения. Если он может быть размещен в стеке, нужны только две инструкции, push и pop, а также снижается нагрузка на сборщик мусора. Таким образом, для сравнения, стоимость размещения в стеке будет намного меньше.

(2) Условия, вызывающие побег

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

1. Указатель

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

(1) Указатель представляет собой косвенный интернет-адрес, который в основном сохраняется в куче, поэтому с учетом GC указатель не обязательно эффективен. Посмотрите на пример

type test struct{}

func main() {
	t1 := test1()
	t2 := test2()
	println("t1", &t1, "t2", &t2)
}

func test1() test {
	t1 := test{}
	println("t1", &t1)
	return t1
}

func test2() *test {
	t2 := test{}
	println("t2", &t2)
	return &t2
}

Запустите, чтобы увидеть ситуацию с побегом (без встраивания)

go run -gcflags '-m -l' main.go
# command-line-arguments
./main.go:36:16: test1 &t1 does not escape
./main.go:43:9: &t2 escapes to heap
./main.go:41:2: moved to heap: t2
./main.go:42:16: test2 &t2 does not escape
./main.go:31:16: main &t1 does not escape
./main.go:31:27: main &t2 does not escape
t1 0xc420049f50
t2 0x10c1648
t1 0xc420049f70 t2 0xc420049f70

Как видно из вышеизложенного, t2 в функции test2, возвращающей указатель, убегает в кучу, ожидание этого будет жестоким GC.

2. Нарезка

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

// 切片超过栈大小
func main(){
	s := make([]byte, 1, 64 * 1024)
	_ = s
}

// 无法确定切片大小
func main() {
	s := make([]byte, 1, rand2.Intn(10))
	_ = s
}

Прочитав приведенный выше пример, давайте рассмотрим интересный пример. Мы знаем, что срезы более эффективны, чем массивы, но так ли это?

func array() [1000]int {
	var x [1000]int
	for i := 0; i < len(x); i++ {
		x[i] = i
	}
	return x
}

func slice() []int {
	x := make([]int, 1000)
	for i := 0; i < len(x); i++ {
		x[i] = i
	}
	return x
}

func BenchmarkArray(b *testing.B) {
	for i := 0; i < b.N; i++ {
		array()
	}
}

func BenchmarkSlice(b *testing.B) {
	for i := 0; i < b.N; i++ {
		slice()
	}
}

Текущий результат выглядит следующим образом

go test -bench . -benchmem -gcflags "-N -l -m"
BenchmarkArray-4        30000000                52.8 ns/op             0 B/op          0 allocs/op
BenchmarkSlice-4        20000000                82.4 ns/op           160 B/op          1 allocs/op

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

3. интерфейс

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

func main() {
	s := "abc"
	fmt.Println(s)
}

Приведенный выше код будет уходить, потому что параметр, полученный методом FMT.Println, имеет тип интерфейса. Но этот кусок только для популярной науки. В конце концов, преимущества интерфейса больше, чем его дефекты.

V. Ссылки

segment.com/blog/Alibaba…