1 Оптимизация памяти
1.1 Небольшие объекты объединяются в структуры и выделяются один раз, чтобы уменьшить количество выделений памяти.
Учащиеся, изучавшие C/C++, могут знать, что частое применение и освобождение небольших объектов в куче вызовет фрагментацию памяти (некоторые из них называются дырами), что приведет к невозможности обращения к непрерывному пространству памяти при выделении больших объектов. заключается в использовании пула памяти. Нижний уровень среды выполнения Go также использует пул памяти, но каждый диапазон имеет размер 4 КБ и одновременно поддерживает кеш. Кэш имеет массив списков в диапазоне от 0 до n. Каждая единица массива списков монтируется со связанным списком. Каждый узел связанного списка является частью доступной памяти. Блоки памяти всех узлов в одном связанном списке равны по размеру, но разные Объем памяти связанного списка неодинаков, то есть единица массива списка хранит тип блока памяти фиксированного размера, а блоки памяти, хранящиеся в разных единицах, неодинаковы по размеру . Это означает, что кеш кэширует объекты памяти разного размера.Конечно, когда размер памяти, на которую вы хотите подать заявку, ближе всего к тому, какой тип блока кэш-памяти, вы должны выделить, какой тип блока памяти. Когда кеша не хватает, он будет выделен spanalloc.
Предложение: небольшие объекты объединяются в структуру для одного выделения, как показано ниже:
for k, v := range m {
k, v := k, v // copy for capturing by the goroutine
go func() {
// using k & v
}()
}
Заменить:
for k, v := range m {
x := struct {k , v string} {k, v} // copy for capturing by the goroutine
go func() {
// using x.k & x.v
}()
}
1.2 Содержимое области кэша Один дистрибутив имеет достаточный размер пространства и соответствующим образом повторно используется
Во время кодирования и декодирования протокола необходимо часто оперировать []byte.Вы можете использовать bytes.Buffer или другие объекты байтового буфера.
Предложение: предварительно выделяя достаточно памяти для байтов. Буфер и т. д., избегайте динамического запроса памяти при увеличении, что может уменьшить количество выделений памяти. В то же время рассмотрите целесообразное повторное использование объектов байтового буфера.
1.3 Когда слайс и карта создаются с помощью make, предполагаемый размер определяет емкость
В отличие от массивов, срезы и карты не имеют фиксированного размера пространства и могут динамически расширяться в соответствии с добавлением элементов.
Слайс изначально будет указывать массив, при добавлении и других операциях над слайсом, когда емкости не хватит, он будет автоматически расширяться:
- Если новый размер более чем в 2 раза превышает текущий размер, емкость увеличится до нового размера;
- Если нет, зациклить следующие операции: если текущая емкость меньше 1024, увеличить в 2 раза, иначе каждый раз увеличивать на 1/4 текущей емкости, пока увеличенная емкость не превысит или не дождется нового размера.
Расширение карты более сложное, и каждое расширение будет увеличивать предыдущую вместимость в 2 раза. В его структуре есть ведра и старые ведра для пошагового расширения:
- При нормальных обстоятельствах ведра используются напрямую, а старые ведра пусты;
- Если емкость расширяется, то старые ведра не пусты и в два раза превышают размер старых ведер.
Рекомендация: Оцените размер и укажите емкость при инициализации.
m := make(map[string]string, 100)
s := make([]string, 0, 100) // 注意:对于slice make时,第二个参数是初始大小,第三个参数才是容量
1.4 Длинный стек вызовов позволяет избежать обращения к более временным объектам
Стек вызовов Goroutine имеет размер 4 КБ (1.7 изменен на 2 КБ), в котором используется механизм непрерывного стека, и когда места в стеке недостаточно, Go Runtime продолжит расширяться:
- Когда места в стеке недостаточно, увеличьте в 2 раза, переменные исходного стека напрямую копируются в новое пространство стека, а указатель переменной указывает на новый адрес пространства;
- Распаковка освободит занятое пространство стека.Когда GC обнаружит, что пространство стека занимает менее 1/4, пространство стека будет уменьшено вдвое.
Например, если окончательный размер стека равен 2М, в крайних случаях будет 10 операций расширения стека, что снизит производительность.
предположение:
- Управляйте сложностью стека вызовов и функций, не выполняйте всю логику в одной горутине;
- Если для проверки требуется длинный стек вызовов, рассмотритеГорутин Чихуа, чтобы избежать частого создания горутин для внесения изменений в пространство стека.
1.5 Избегайте частого создания временных объектов
Go вызовет остановку мира во время GC, то есть приостановит все потоки пользовательской логики. Хотя версия 1.7 значительно оптимизировала производительность сборщика мусора, в версии 1.8 даже в неблагоприятных условиях сборщик мусора составляет 100 мкс. Однако время паузы по-прежнему зависит от количества временных объектов, чем больше временных объектов, тем дольше может быть время паузы и потребляется процессор.
Рекомендация: Метод оптимизации GC заключается в том, чтобы максимально уменьшить количество временных объектов:
- Попробуйте использовать локальные переменные (распределение стека)
- Несколько частичных переменных сочетают в себе большую структуру или массив (аналогично 1.1), уменьшают количество отсканированных объектов и возвращайтесь как можно больше.
2 одновременная оптимизация
2.1 Используйте пул горутин для высокопараллельной обработки задач
Несмотря на то, что горутины легкие, для обработки легковесных задач с высокой степенью параллелизма, если горутины часто создаются для выполнения, эффективность выполнения будет не слишком эффективной:
- Создание слишком большого количества горутин повлияет на планирование горутин в среде выполнения и потребление GC;
- Если во время высокого уровня параллелизма возникает аномальная блокировка вызовов, большое количество невыполненных операций горутин за короткое время может привести к сбою программы.
2.2 Избегайте большого количества одновременных вызовов интерфейсов синхронной системы
Задача горутины — имитировать асинхронную операцию путем синхронизации. Планирование потоков не блокирует работу среды выполнения следующим образом:
- сетевой ввод-вывод
- Замок
- channel
- time.sleep
- Системный вызов на основе базовых системных асинхронных вызовов
Следующая блокировка создает новый поток планирования:
- локальные вызовы ввода-вывода
- Системный вызов на основе базового системного синхронного вызова
- Вызов ввода-вывода или другой блокировки в динамической библиотеке языка C с помощью CGo
Сетевой ввод-вывод может быть основан на асинхронном механизме epoll (или асинхронном механизме, таком как kqueue), но он не обеспечивает асинхронный механизм для некоторых системных функций. Например, в общем API posix работа с файлом является синхронной операцией. Хотя существует файловый опрос с открытым исходным кодом для имитации асинхронных операций с файлами. Но системный вызов Go по-прежнему зависит от API базовой операционной системы. Системный API не является асинхронным, и Go не может выполнять асинхронную обработку.
Предложение: изолируйте горутины, участвующие в синхронных вызовах, в управляемые горутины вместо того, чтобы напрямую вызывать горутины с высокой степенью параллелизма.
2.3 Избегайте взаимного исключения общих объектов при высокой степени параллелизма
В традиционном многопоточном программировании, когда конфликт параллелизма составляет от 4 до 8 потоков, производительность может иметь точку перегиба. В Go рекомендуется не обмениваться данными через общую память. В Go очень легко создавать горутины. Когда большое количество горутин совместно используют один и тот же объект мьютекса, для определенного количества горутин наступит переломный момент.
предположение:
. 1), насколько это возможно, независимая горутина, бесконфликтное выполнение, если есть конфликт между горутиной, раздел может быть принят для контроля количества одновременных горутин, чтобы уменьшить параллельный конфликт того же мьютекса.
2) Используя разделы, разделите данные, которые нуждаются в защите взаимного исключения, на несколько фиксированных разделов (рекомендуется, чтобы они были целым числом, кратным 2, например 256), и найдите разделы (не взаимоисключающие) при доступе, чтобы несколько процессов Go могли быть уменьшена Вероятность конкуренции за 1 раздел данных.
3 Другие оптимизации
3.1 Избегайте использования CGO или уменьшите количество вызовов CGO
Go может вызывать библиотечные функции C, но в Go есть сборщик мусора, а стек Go динамически растет, но они не могут быть напрямую связаны с C. Прежде чем среда Go будет передана для выполнения кода C, для C должен быть создан новый стек вызовов, переменная стека назначена стеку вызовов C, вызов завершен и скопирован обратно. И эти накладные расходы на вызовы также очень велики, и необходимо поддерживать контекст вызова Go и C, а также сопоставление стеков вызовов этих двух. По сравнению со стеком вызовов прямого GO чистый стек вызовов может иметь более 2 или даже 3 порядка.
Предложение: по возможности избегайте использования CGO.Когда это неизбежно, уменьшите количество вызовов через CGO.
3.2 Уменьшите преобразование между []byte и string, попробуйте использовать []byte для обработки строк
Строковый тип в GO — неизменяемый тип, в отличие от std:string в C++, его можно напрямую преобразовать в значение char*, чтобы указать на то же содержимое адреса, в то время как в GO []byte и string — это две разные структуры внизу. , между ними есть реальная копия объекта значения для преобразования, поэтому постарайтесь уменьшить это ненужное преобразование
Предложение: если есть обработка, такая как объединение строк, попробуйте использовать []byte, например:
func Prefix(b []byte) []byte {
return append([]byte("hello", b...))
}
3.3 Конкатенация строк отдает приоритет байтам. Буфер
Поскольку строковый тип является неизменяемым, при конкатенации создается новая строка. В GO есть несколько распространенных способов сращивания строк:
- строка + операция: вызывает несколько распределений объектов и копий значений
- FMT.SPrintf: Динамически параметры анализа, которые не пошли большей эффективности
- strings.Join : внутренне добавляется []byte
- bytes.Buffer: размер может быть предварительно выделен, что уменьшает выделение и копирование объектов.
Рекомендация: Для высоких требований к производительности отдавайте предпочтение байтам. Буфер, заранее выделяйте размер. Некритические пути используются для краткости. fmt.Sprintf может упростить преобразование и объединение различных типов.
Ссылка на ссылку:
Блог Woohoo.cn на.com/Zhang Boyu/Afraid…