Обмен технологиями: управление памятью

Go

Введение: как написать техническую статью

  1. Определите целевую аудиторию
  2. Начиная с проблемы, шаг за шагом преодолевая проблему, чтобы вывести содержание, которое необходимо объяснить.
  3. Получение контента с логикой на основе проблем
    1. иметь логику
  4. Принципы Фибоначчи
    1. слой за слоем

PS: от Zhihu Q&A


текст

Тема этого обмена: управление памятью. Прежде всего расскажу о том, зачем я сделал этот шеринг.Я много чего видел раньше, но так как не использовал в своей работе, то после прочтения забыл об этом.В качестве примера возьмем redis, я Поверьте, многие люди читали «Дизайн Redis» в книге «И реализация», я помню, как читал исходный код во время чтения книги в то время, но сейчас я ничего не помню. Итак, я просто хотел иметь способ лучше усвоить и понять то, что я узнал.Даже если это содержание временно недоступно на работе, хороший способ - пройти викторины, то есть мы часто задаем какие-то вопросы.Я буду проверять свои понимание содержания само по себе, поэтому я буду задавать вопросы сначала в этом обмене, а затем, чтобы ответить на этот вопрос, я буду давать содержание этого обмена шаг за шагом.Конечно, первое, потому что тема управления памятью слишком велик. Во-вторых, поскольку знания, которые вы знаете, ограничены, невозможно охватить все, потому что вы не знаете того, чего не знаете. Мы можем только продолжать дополнять и улучшать содержание управления памятью в будущем.

выделение памяти

Давайте сначала поговорим о распределении памяти.Давайте посмотрим на следующий небольшой код:

func main() {
	http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
	})
    fmt.Printf("pid: %d\n", os.Getpid())
	http.ListenAndServe(":8080", nil)
}

Функция очень простая, достаточно запустить http сервис на порту 8080, компилируем и запускаем

go build main.go
./main
> pid: 3240424

Просмотр сведений о процессе с помощью команды ps (ps под Mac)

Мы ориентируемся на два показателя:ВСЗ, РСС. VSZ — это аббревиатура от Virtual Memory Size, а RSS — это аббревиатура от Resident Set Size, которая представляет фактическое использование физической памяти программой. Это очень странно.Мы видим, что виртуальная память программы занимает около 213,69 МБ, а физическая память занимает 5,30 МБ.Тогда возникает проблема:

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


Виртуальная память

Говоря о виртуальной памяти,Начнем с системы фон Неймана Система фон Неймана в основном делит компьютер на три части: процессор, память и ввод-вывод.Давайте сначала ответим на вопрос: как исполняемая программа может быть выполнена? Мы разрабатываемgo build main.go; ./mainПочему программа запускается, когда мы запускаем main?

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

file output/bin/xx.api
output/bin/xx.api: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, not stripped

несколько ключевых словELF, динамически связанный, ld-linux-x86-64.so.2, объясняем отдельно. Во-первых, ELF — это тип исполняемой программы под Linux, мы можемman elfСм. специальные инструкции,

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

Сначала взгляните на заголовок эльфа, мы можем пройтиreadelf -h output/bin/x.apiПроверять

Конкретное значение заголовка elf определено в файле elf.h, который имеет структуру Elf64_Ehdr, а значение каждого поля можно найти в руководстве. На 64-битной машине размер заголовка elf составляет 64 байта, и мы можем передать командуhexdump -C output/bin/xx.api |head -n 10Чтобы просмотреть данные, а затем сравнить их с реальной ситуацией, позаимствуйте онлайн-картинку:

Дополнительную информацию об эльфе смотрите в блогеIntroduction to the ELF Format

После введения заголовка elf, следующие две очень важные концепции:

  • program header
  • Section header

Мы знаем, что программа должна пройти четыре этапа предварительной обработки, компиляции, сборки и компоновки, чтобы стать исполняемым файлом. Сборка — это инструкция по преобразованию ассемблерного файла в машинный исполняемый файл. Еще одна очень важная задача — классифицировать файл по к семантике Сегментное хранилище, некоторые общие разделы — это сегменты кода, сегменты данных, сегменты отладки и т. д., так почему же мы сегментируем по разным функциям? В качестве примера возьмем следующий код:

int printf(const char *format, ...);

void func1(int i) {
    printf("%d\n", i);
}

int main() {
    static int static_var = 85;
    static int static_var2;

    int a = 1;
    int b;

    func1(static_var + static_var2 + a + b);

    return a;
}

В приведенном выше коде мы объявляем printf, но не определяем его. Нам нужно переместить символ printf на этапе связывания. Чтобы найти символ printf в других файлах, в естественном файле должна быть таблица символов экспорта, которая удобен для других файл для поиска символов, поэтому мы заказываемУлучшенная функция делителя, а так же удобно для поиска при линковке, чтении отладочной информации и т.п., есть Заголовок Раздела, можем пройтиreadelf -S output/bin/xx.apiдля просмотра заголовка раздела

Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

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

Наконец, давайте представим заголовок программы. Теперь у нас есть исполняемый файл, и через 64 ​​байта в начале файла мы можем проверить, что файл в формате elf.После того, как проверка пройдена, чтобы иметь возможность выполнить файл, нам, естественно, нужно Файл загружается в память, и только загруженная программа может выполняться. Вопрос как загрузить программу?

давайте пройдемсяreadelf -l output/bin/xx.apiдля просмотра заголовка программы программы

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

  • offset: смещение в файле
  • virtAddr: виртуальное адресное пространство, то есть адрес в адресном пространстве процесса после загрузки программы
  • fileSize: сегмент занимает большой размер в файле
  • memSize: размер виртуального пространства, занимаемого сегментом, загружаемым в память.

На этом этапе мы суммируем заголовок программы и заголовок раздела.

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

На приведенном выше рисунке показано отношение между разделом и сегментом, изображение взято изInterfacing with ELF filesЕще одно статическое представление исполняемых программ и динамическое представление в памяти.

Изображение изExecutable and Linkable Format 101

TLB

Теперь мы перестраиваем наше мышление:

С помощью команды ps мы можем видеть, что виртуальная память программы занимает намного больше, чем физическая память.Чтобы ответить, почему существует это явление, мы должны сначала узнать, что такое виртуальная память, поэтому мы вводим исполняемую программу, которая представляет собой статический вид на диске: исполняемая программа делится на разные секции по разным функциям.При загрузке программы в память разные секции образуют сегмент по одинаковым разрешениям и распределяются по виртуальному адресному пространству программы.

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

Во-первых, выполнение программы на самом деле является выполнением процессором инструкций строка за строкой.ЦП нужен адрес для чтения памяти и последующего ее выполнения.Этот адрес является самым ранним адресом физической памяти, что означает, что когда все программы связаны ,они должны указывать друг друга.Разные физические адреса,иначе они будут загружаться в память и перезаписываться друг другом.Это жесткое требование очевидно невыполнимо при большем количестве программ.Поэтому существует виртуальное адресное пространство,т.е каждая программа имеет свое собственное виртуальное адресное пространство процессора, а затем процессор видит виртуальное адресное пространство, но для доступа к физической памяти требуется физический адрес, поэтому существует промежуточное программное обеспечение для преобразования виртуального адреса в физический адрес.

evernotecid://684F00FC-2900-4AF6-B7AA-D9B72CB9AC48/appyinxiangcom/5364228/ENResource/p14937

На картинке выше показана аппаратная структура управления памятью, картинка взята изVirtual Memory, Paging, and SwappingВесь процесс трансляции заключается в том, что ЦП выполняет адресацию виртуальных адресов.В это время существует MMU: блок управления памятью, который отвечает за преобразование виртуальных адресов в физические адреса.Из-за большого разрыва между скоростью ЦП и скорость физической памяти (примерно в 200 раз ниже), поэтому будет TLB: Translation Lookaside Buffer (трансляционный резервный буфер) специально для кэширования этого отношения сопоставления, а затем это отношение сопоставления необходимо реализовать в таблице страниц, когда оно Обнаружено, что виртуальному адресу не был назначен физический адрес.Когда есть место, будет запущено прерывание ошибки страницы.В это время он проверит, какой виртуальный адрес соответствует содержимому файла, загрузит его в память, и установить отношение отображения в таблице страниц, и программа может продолжить выполнение.

В ответ на процесс, описанный выше, давайте ответим на несколько вопросов:

  1. Что такое таблицы страниц и для чего они используются?
  2. Что кешируется в TLB?

Начнем с ответа, зачем нужны таблицы страниц? Наша цель сейчас состоит в том, чтобы установить отношение отображения между виртуальными адресами и физическими адресами.В общем, мы можем рассматривать память как большой массив.Каждый элемент в массиве имеет размер 1 байт, что означает, что физический размер 1G памяти нам нужно Отношение отображения 4G, то есть нам нужно 4 байта для отношения, простое представление

var maps [4*1024*1024*1024]int32
// 下标就是虚拟地址,值就是物理地址

То есть для отображения памяти 4G нам нужно 16G для хранения отношения отображения. Это заведомо невозможно, поэтому нужно энергично делить физическую память.Как правило, в эпоху 32-битных машин мы делим физическую память на страницы, и размер каждой страницы равен 4К.Для удобства будем считать, что виртуальный адрес также делится на страницы.В настоящее время 4G разделен на страницы 1M, 4M необходимо для хранения этого отношения сопоставления, а память 4M требует 1000 страниц. В настоящее время, даже если физическая память имеет 4G, мы можем одновременно запускать только 1000 страниц, сохраняя таблицу страниц процесса. Поэтому мы используем многоуровневую схему таблицы страниц.На следующем рисунке показан пример уровня 2:

Изображение изTLB and Pagewalk Coherence in x86 ProcessorsЕсли мы разделим виртуальный адрес на 4М, для первого отношения отображения потребуется только 1К элементов, то есть 4К памяти, одна физическая страница.


Выше мы ответили, зачем нужны таблицы страниц, ниже отвечаем, что кэшируется в TLB? Сначала посмотрим на картинку: вечноноцид://684F00FC-2900-4AF6-B7AA-D9B72CB9AC48/appyinxiangcom/5364228/ENResource/p14939

Изображение изCPU Cache Flushing FallacyКак видно из приведенного выше рисунка, скорость доступа к памяти более чем в 60 раз превышает скорость процессора, поэтому, если доступ к основной памяти осуществляется каждый раз при преобразовании виртуального адреса в физический, скорость явно невыносима, поэтому мы иметь TLB в качестве кеша для ускорения доступа. Мы можем использовать командуcpuidдля просмотра информации tlb,

cpuid -1
L1 TLB/cache information: 2M/4M pages & L1 TLB (0x80000005/eax):
      instruction # entries     = 0xff (255)
      instruction associativity = 0x1 (1)
      data # entries            = 0xff (255)
      data associativity        = 0x1 (1)
L1 TLB/cache information: 4K pages & L1 TLB (0x80000005/ebx):
  instruction # entries     = 0xff (255)
  instruction associativity = 0x1 (1)
  data # entries            = 0xff (255)
  data associativity        = 0x1 (1)
L2 TLB/cache information: 2M/4M pages & L2 TLB (0x80000006/eax):
      instruction # entries     = 0x0 (0)
      instruction associativity = L2 off (0)
      data # entries            = 0x0 (0)
      data associativity        = L2 off (0)
L2 TLB/cache information: 4K pages & L2 TLB (0x80000006/ebx):
  instruction # entries     = 0x200 (512)
  instruction associativity = 4-way (4)
  data # entries            = 0x200 (512)
  data associativity        = 4-way (4)

адрес онлайн-просмотраCache Organization on Your MachineВыше мы видим, что TLB также делится на L1 и L2, как кеш.Если кеш L1 кэширует большие страницы 2M/4M, то есть 256 элементов, а маленькие страницы 4k также содержат 256 элементов, кэш L2 может кэшировать только 512 маленьких 4k страниц. Кроме того, TLB также делится на TLB инструкций (ITLB) и TLB данных (DTLB), как кэш.

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

Теперь мы знаем, что таблица страниц используется для хранения отношения сопоставления между виртуальными адресами и физическими адресами, а также мы знаем, что для ускорения процесса преобразования TLB хранит это отношение сопоставления в виде кеша, но мы задумаемся об этом, мы сказали, что пространство 4G основано на страницах 4K. Если оно разделено, будет 1K записей. Прежде чем мы прошлиcpuidПри проверке команды обнаруживается, что даже в TLB 2 всего 512 элементов, а значит, необходимо сделать сопоставление 1024 -> 512. Как это сделать? Есть еще один вопрос: когда мы смотрели информацию о TLB раньше,instruction associativity = 4-way (4)концепция, что это значит?

Мы думаем, что наш текущий виртуальный адрес 4G разбит на 1К копий, каждая из которых имеет размер 4М.Для 32-битного адреса первые 10 бит это номер страницы, а последние 22 бита это смещение страницы, но мы теперь есть только 256 TLB Элементы таблицы, самый простой способ - сравнить все элементы таблицы, чтобы увидеть, есть ли виртуальный номер страницы в элементах таблицы 256. Это требует одновременного сравнения строк 256. По мере увеличения этого числа оно будет становится все сложнее. , поэтому у нас есть еще один метод.

Мы используем последние 8 бит 10-битного номера виртуальной страницы для выбора 256 записей и используем старшие 2 бита для сравнения, является ли это текущей строкой.

evernotecid://684F00FC-2900-4AF6-B7AA-D9B72CB9AC48/appyinxiangcom/5364228/ENResource/p14943

схема Дополнительный вопрос: почему мы не используем старшие биты для выбора групп и младшие биты для сравнения тегов? вечноноцид://684F00FC-2900-4AF6-B7AA-D9B72CB9AC48/appyinxiangcom/5364228/ENResource/p14944
Картинка взята из "Глубокого понимания компьютерных систем" Причина проста: если мы используем старшие биты для выбора групп, но так как в каждой группе только одна, это означает, что соседние группы отображаются на одну и ту же строку кеша, и локальность программы не годится.

Тогда здесь ассоциативность = 4-сторонняя Это означает, что есть 4 группы, то есть 2 бита для выбора группы, а остальные биты используются для сравнения тегов. Подробнее см. Главу 6 «Углубленное понимание компьютера». Системы», о котором написано очень подробно.


резюме

Резюмируя, что мы имеем на данный момент:

  • Прежде всего, тема, которую мы разделяем на этот раз, — это управление памятью, которое будет основано на выделении памяти и сборке мусора.
  • В первой части выделения памяти мы начнем с явления, когда виртуальная память и физическая память очень велики после выполнения простой программы Почему мы хотим вернуться?
  • Чтобы ответить, почему виртуальная память намного больше физической памяти, мы должны сначала узнать, что такое виртуальная память.
  • Чтобы объяснить виртуальную память, мы вводим два представления исполняемых программ:
    • Статическое представление: формат эльфа на диске, и он хранится в разделах в соответствии с функциями программы.
    • Динамический вид: при загрузке в память разделы с одинаковыми разрешениями будут объединены и размещены в одном виртуальном адресном пространстве.
  • Когда мы упоминали о динамическом представлении процесса выше, мы пытались ответить, почему каждая программа имеет свое виртуальное адресное пространство.
    • Изоляция: каждая программа может назначить свой собственный адрес выполнения программы на этапе связывания.
    • Безопасность: каждая программа может получить доступ только к содержимому своего адресного пространства.
  • Потому что каждая программа имеет свое собственное виртуальное адресное пространство, но инструкции и данные, выполняемые реальной машиной, должны храниться в физической памяти, что требует преобразования виртуального адреса -> физического адреса.
  • При переводе, чтобы сохранить соответствие между виртуальными адресами и физическими адресами, у нас есть таблицы страниц, а чтобы уменьшить место, занимаемое таблицами страниц, у нас есть многоуровневые таблицы страниц
  • Из-за огромной разницы между скоростью процессора и памяти у нас нет возможности каждый раз читать таблицу страниц в памяти.У всех есть TLB, а TLB по сути является кешем.Поскольку емкость кеша меньше, чем все соответствующие отношения, это необходимо решить:
    • Быстро узнать, есть ли корреспонденция в TLB
    • Устранить, когда TLB заполнен
    • Обеспечение согласованности данных в TLB и данных памяти
    • . . . .
    • Вышеупомянутые проблемы не были специально разработаны в этот раз и будут добавлены позже.отметка. Обновить адрес статьи

Теперь, когда у нас есть вышеуказанные знания, давайте опишем в целом, что мы делаем в ежедневной разработке.go build main.go; ./mainЧто произошло, когда и почему программа была выполнена?

  1. Поскольку мы выполняем эту команду в bash, все bash сначала создадут новый процесс через fork
  2. Новый процесс задает выполнение файла elf через системный вызов execve, то есть основной файл
  3. execve входит в режим ядра через системный вызов sys_execve
  4. Ссылка вызова ядра: sys_execve -> do_execve -> search_binary_handle -> load_elf_binary
  5. Основной процесс сначала читает первые 128 слов для определения формата файла, а затем выбирает соответствующий загрузчик для загрузки программы в память.
    1. Загрузка в память здесь просто считывает заголовок программы, выделяет виртуальное адресное пространство и устанавливает отношение отображения между виртуальным адресным пространством и исполняемым файлом.
    2. Отношение отображения между виртуальным пространством и исполняемым файлом устанавливается здесь для правильной загрузки содержимого в случае исключения ошибки страницы.
    3. Когда происходит page fault, выделяется физическая страница, а затем загружается файл с диска, в это время реально занята физическая память.
  6. Наконец, мы устанавливаем регистр инструкций процессора на адрес входа исполняемого файла. Программа начинает выполняться. . . .

Я считаю, что с помощью приведенного выше содержания очень легко ответить на вопрос в начале.Когда каждая программа загружается, выделяется адрес виртуального пространства, но только когда эти адреса фактически доступны, будет запущено прерывание ошибки страницы и физический память будет выделена. пс:cat /proc/21121/maps; cat /proc/21121/smapsВы можете просмотреть подробное адресное пространство процесса.

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

Мы ввели понятие виртуальной памяти выше.Мы знаем, что программа должна быть выполнена и загружена в память через уровни шагов, прежде чем она может быть выполнена.Если мы наслаиваем управление памятью вышеупомянутого содержимого, оно должно принадлежать к уровню ядра и содержимому аппаратного уровня (TLB, кэш), сначала приходим к картинке: вечноноцид://684F00FC-2900-4AF6-B7AA-D9B72CB9AC48/appyinxiangcom/5364228/ENResource/p14945

Изображение взято из «Запуск ядра Linux». ps: Эта книга также является списком книг для прочтения, но сценария применения в своей работе я пока не нашел.Возможно сначала использовать как справочник. Наше предыдущее содержание было посвящено очень небольшому управлению памятью на уровне ядра и аппаратном уровне. Теперь мы хотим поговорить об управлении памятью на уровне приложения. Прежде всего, мы должны знать, почему ядро ​​имеет управление памятью, и нам еще нужно сделать это на прикладном уровне управление памятью.

  1. Уменьшить системные вызовы (brk, mmap)
  2. Операционная система не умеет повторно использовать память.Как только процесс обращается за памятью, эта память занята процессом.Пока она не освобождена, мы больше не можем выделять ее другим.
  3. Прикладной уровень самостоятельно управляет памятью, что позволяет лучше повторно использовать память и может взаимодействовать со сборщиком мусора для лучшего управления памятью.

Давайте посмотрим на реализации управления памятью на распространенных языках:

  • C: malloc, бесплатно
  • С++: новый, удалить
  • Go: анализ побега и сборка мусора

Практические проекты:Как реализовать минимальный malloc самостоятельно.

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


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

  • пропускная способность
  • использование памяти

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

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

Давайте взглянем на структуру распределителя памяти в языке GO, разделив ее на две части:

  • tcmalloc
  • Реализация распределителя языка Go

tcmalloc

tcmalloc хорошо известен, в Интернете есть много контента для анализа tcmalloc. Рекомендовать статью здесьГрафический TCMalloc, одна картинка стоит тысячи слов. Упомяните только, почему tcmalloc так хорош здесь.

  • Рассеять конфликты за блокировку через локальный кэш потока (одна блокировка становится большим количеством блокировок)
  • Есть заимствование и погашение, а когда есть сплошная память, которая не используется, она возвращается ядру

ps: Очень интересно вернуть память операционной системе на 64-битной машине.При освобождении памяти ВА не освобождается, а ВА висит на куче.Это просто предложение к операционным системы. Если определенное виртуальное устройство временно не используется, виртуальное устройство и PA могут быть удалены. Сопоставление mmu, операционная система может вызвать два поведения. Первое поведение заключается в том, что физической памяти операционной системы действительно недостаточно. много операций загрузки и выгрузки, поэтому она будет освобождена. , но, зная, что это пространство PA недоступно, виртуальная машина не освобождается в куче, и следующее распределение использует освобожденную в это время виртуальную машину, что может вызвать два Во-первых, сопоставление PA не освобождается напрямую. Возьмите его и используйте. Во-вторых, при удалении PA операционная система пропустит страницу, и операционная система компенсирует это. это физическая память Этот процесс невидим для пользовательского пространства, поэтому в пользовательском пространстве он ощущается так Память сегмента вообще не освобождалась, потому что пользовательское пространство всегда видит ВА, а определенный сегмент памяти на ВА может существовать, а может и не существовать.Существует он или нет для пользовательской логики вообще не имеет значения, этим местом фактически управляет операционная система, операционная система устанавливает сопоставление через mmu, что вызовет 64-битный ВА адресное пространство должно быть очень большим, пока приложение не будет выпущено, оно будет повторно использовано в следующий раз, но повторное использование компенсирует это. Сейчас управление памятью намного проще под 64-битной, она как раз используется для ВА, и ее выпускать не будут. Операционная система Windows не рекомендует ее выпускать, она может только сказать, что она вся выпущена.

go allocator

Рекомендуемый материал здесьGo's Memory Allocator - Overview GopherCon 2018 - Allocator WrestlingСказано лишь, что выделение памяти в go происходит от tcmalloc, и основная идея такая же.Ниже представлена ​​упрощенная схема: вечноноцид://684F00FC-2900-4AF6-B7AA-D9B72CB9AC48/appyinxiangcom/5364228/ENResource/p15012

иди вынос мусора

рекомендуется хорошая информацияGolang's Realtime GC in Theory and Practice Getting to Go: The Journey of Go's Garbage Collector

Тюнинг

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

Ссылаться на

Go Memory Management CppCon 2017: Боб Стигал «Как написать собственный распределитель Go's Memory Allocator - Overview Golang's Realtime GC in Theory and Practice GopherCon 2018 - Allocator Wrestling Golang's Realtime GC in Theory and Practice Getting to Go: The Journey of Go's Garbage Collector