Это отрывок из сообщения; полный пост доступен здесь: https://victoriametrics.com/blog/go-sync-pool/
Этот пост является частью серии об управлении параллелизмом в Go:
В исходном коде VictoriaMetrics мы часто используем sync.Pool, и, честно говоря, он отлично подходит для обработки временных объектов, особенно байтовых буферов или срезов.
Он обычно используется в стандартной библиотеке. Например, в пакетеcoding/json:
package json var encodeStatePool sync.Pool // An encodeState encodes JSON into a bytes.Buffer. type encodeState struct { bytes.Buffer // accumulated output ptrLevel uint ptrSeen map[any]struct{} }
В этом случае sync.Pool используется для повторного использования объектов *encodeState, которые обрабатывают процесс кодирования JSON в байты.Buffer.
Вместо того, чтобы просто выбрасывать эти объекты после каждого использования, что только усложнит работу сборщику мусора, мы прячем их в пуле (sync.Pool). В следующий раз, когда нам понадобится что-то подобное, мы просто возьмем это из пула, а не создадим новое с нуля.
В пакете net/http вы также найдете несколько экземпляров sync.Pool, которые используются для оптимизации операций ввода-вывода:
package http var ( bufioReaderPool sync.Pool bufioWriter2kPool sync.Pool bufioWriter4kPool sync.Pool )
Когда сервер считывает тела запросов или записывает ответы, он может быстро извлечь из этих пулов предварительно выделенное устройство чтения или записи, пропуская дополнительные выделения. Кроме того, два пула записи, *bufioWriter2kPool и *bufioWriter4kPool, настроены для удовлетворения различных потребностей записи.
func bufioWriterPool(size int) *sync.Pool { switch size { case 2Ладно, хватит вступления.
Сегодня мы углубимся в то, что такое sync.Pool, его определение, как он используется, что происходит под капотом и все остальное, что вы, возможно, захотите узнать.
Кстати, если вам нужно что-то более практичное, есть хорошая статья от наших экспертов по Go, показывающая, как мы используем sync.Pool в VictoriaMetrics: методы оптимизации производительности в базах данных временных рядов: sync.Pool для операций, связанных с процессором
]Что такое синхронизирующий пул?
Проще говоря, sync.Pool в Go — это место, где вы можете хранить временные объекты для последующего повторного использования.
Но вот в чем дело: вы не контролируете, сколько объектов остается в пуле, и все, что вы туда помещаете, может быть удалено в любой момент без какого-либо предупреждения, и вы поймете почему, прочитав последний раздел.
]Хорошим моментом является то, что пул создан как потокобезопасный, поэтому к нему могут одновременно подключаться несколько горутин. Неудивительно, учитывая, что это часть пакета синхронизации.
"Но зачем нам повторно использовать объекты?"
Когда одновременно выполняется много горутин, им часто нужны похожие объекты. Представьте, что вы запускаете go f() несколько раз одновременно.
Если каждая горутина создает свои собственные объекты, использование памяти может быстро увеличиться, и это создает нагрузку на сборщик мусора, поскольку ему приходится очищать все эти объекты, когда они больше не нужны.
Эта ситуация создает цикл, в котором высокий уровень параллелизма приводит к высокому использованию памяти, что затем замедляет работу сборщика мусора. sync.Pool призван помочь разорвать этот порочный круг.
type Object struct { Data []byte } var pool sync.Pool = sync.Pool{ New: func() any { return &Object{ Data: make([]byte, 0, 1024), } }, }Чтобы создать пул, вы можете предоставить функцию New(), которая возвращает новый объект, когда пул пуст. Эта функция необязательна. Если вы ее не предоставите, пул просто вернет ноль, если он пуст.
В приведенном выше фрагменте цель состоит в том, чтобы повторно использовать экземпляр структуры Object, а именно фрагмент внутри него.
Повторное использование среза помогает уменьшить ненужный рост.
Например, если размер фрагмента во время использования вырастет до 8192 байт, вы можете сбросить его длину до нуля, прежде чем помещать его обратно в пул. Базовый массив по-прежнему имеет емкость 8192 байта, поэтому в следующий раз, когда они вам понадобятся, эти 8192 байта будут готовы к повторному использованию.
func (o *Object) Reset() { o.Data = o.Data[:0] } func main() { testObject := pool.Get().(*Object) // do something with testObject testObject.Reset() pool.Put(testObject) }Последовательность действий довольно ясна: вы получаете объект из пула, используете его, сбрасываете его, а затем помещаете обратно в пул. Сброс объекта можно сделать либо до того, как вы поместите его обратно, либо сразу после того, как вы достанете его из пула, но это не обязательно, это обычная практика.
Если вы не являетесь поклонником использования утверждений типовpool.Get().(*Object), есть несколько способов избежать этого:
func getObjectFromPool() *Object { obj := pool.Get().(*Object) return obj }
type Pool[T any] struct { sync.Pool } func (p *Pool[T]) Get() T { return p.Pool.Get().(T) } func (p *Pool[T]) Put(x T) { p.Pool.Put(x) } func NewPool[T any](newF func() T) *Pool[T] { return &Pool[T]{ Pool: sync.Pool{ New: func() interface{} { return newF() }, }, } }
Общая оболочка обеспечивает более типобезопасную работу с пулом, избегая утверждений типов.
Обратите внимание, что это добавляет немного накладных расходов из-за дополнительного уровня косвенности. В большинстве случаев эти накладные расходы минимальны, но если вы работаете в среде с высокой чувствительностью к процессору, рекомендуется запустить тесты, чтобы увидеть, стоит ли оно того.
Но подождите, это еще не все.
Если вы заметили из многих предыдущих примеров, в том числе из стандартной библиотеки, в пуле обычно хранится не сам объект, а указатель на объект.
Позвольте мне объяснить почему на примере:
var pool = sync.Pool{ New: func() any { return []byte{} }, } func main() { bytes := pool.Get().([]byte) // do something with bytes _ = bytes pool.Put(bytes) }
Мы используем пул размером в []байт. Обычно (хотя и не всегда), когда вы передаете значение в интерфейс, это может привести к помещению значения в кучу. Это происходит и здесь, не только со срезами, но и со всем, что вы передаете в пул.Put(), что не является указателем.
Если вы проверяете с помощью escape-анализа:
// escape analysis $ go build -gcflags=-m bytes escapes to heap
Я не говорю, что наша переменная bytes перемещается в кучу, я бы сказал: «значение байтов уходит в кучу через интерфейс».
Чтобы понять, почему это происходит, нам нужно разобраться, как работает escape-анализ (что мы могли бы сделать в другой статье). Однако если мы передаем указатель в Pool.Put(), дополнительного выделения не будет:
var pool = sync.Pool{ New: func() any { return new([]byte) }, } func main() { bytes := pool.Get().(*[]byte) // do something with bytes _ = bytes pool.Put(bytes) }
Запустите escape-анализ еще раз, и вы увидите, что он больше не переходит в кучу. Если вы хотите узнать больше, в исходном коде Go есть пример.
Прежде чем мы перейдем к тому, как на самом деле работает sync.Pool, стоит разобраться с основами модели планирования PMG Go, это действительно основа того, почему sync.Pool так эффективен.
Есть хорошая статья, в которой модель PMG представлена с некоторыми визуальными эффектами: модели PMG в Go
Если вам сегодня лень и вы ищете упрощенное изложение, я вас поддержу:
PMG означает P (логические pпроцессоры), M (mмашинные потоки) и G (gили процедуры). Ключевым моментом является то, что на каждом логическом процессоре (P) в любой момент времени может работать только один машинный поток (M). А для запуска горутины (G) ее необходимо присоединить к потоку (M).
Это сводится к двум ключевым моментам:
Но дело в том, что синхронизация. Пул в Go — это не просто один большой пул, он на самом деле состоит из нескольких «локальных» пулов, каждый из которых привязан к определенному контексту процессора, или P, который определяет среду выполнения Go. управление в любой момент времени.
Когда горутина, работающая на процессоре (P), нуждается в объекте из пула, она сначала проверяет свой собственный P-локальный пул, прежде чем искать что-либо еще.
Полную публикацию можно найти здесь: https://victoriametrics.com/blog/go-sync-pool/
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3