1 Preface
In a nutshell: save and reuse temporary objects, reduce memory allocation, and reduce GC pressure.
sync.Pool works roughly as follows.
- A local object pool poolLocal is created for each P to minimize concurrency conflicts, taking advantage of GMP features.
- Each poolLocal has a private object, and access to private objects is given priority to avoid complex logic.
- Use
pin
to lock the current P during Get and Put to prevent the goroutine from being preempted and causing program chaos. - Use object stealing mechanism to fetch objects from other P’s local object pool and victim during Get.
- Make full use of CPU Cache feature to improve program performance.
As a simple example.
The deserialization of json is very common in text parsing and network communication, and when the program is very concurrent, a large number of temporary objects need to be created in a short time. These objects are allocated on the heap, which will put a lot of pressure on the GC and seriously affect the performance of the program.
Since version 1.3, Go has provided a mechanism for object reuse, the sync.Pool. sync.Pool is scalable and concurrency-safe, and its size is limited only by the size of memory. sync.Pool is used to store values that have been allocated but not used, and may be used in the future. This allows the system to reuse existing objects without having to go through memory allocation again, reducing the pressure on GC and thus improving system performance.
The size of the sync.Pool is scalable and will be dynamically expanded at high load, and objects stored in the pool will be automatically cleaned up if they become inactive.
2 How to use
The use of sync.Pool is very simple.
2.1 Declaring a pool of objects
You only need to implement the New function. If there are no objects in the pool, the New function will be called to create them.
2.2 Get & Put
Get()
is used to get an object from the object pool, because the return value isinterface{}
and therefore requires a type conversion.Put()
, on the other hand, returns the object pool after the object is used.
3 Performance tests
3.1 struct deserialization
|
|
The test results are as follows.
In this example, because the Student structure has a small memory footprint, memory allocation takes almost no time at all. The standard library json deserialization uses reflection, which is less efficient and takes up most of the time, so the final execution time between the two methods is almost unchanged. However, the memory footprint is an order of magnitude worse. After using sync.Pool
, the memory footprint is only 234/5096 = 1/22 of unused, which has a big impact on GC.
3.2 bytes.Buffer
|
|
The test results are as follows.
This example creates a pool of bytes.Buffer
objects and performs only one simple Write
operation at a time, which is a pure memory mover and takes almost negligible time. Memory allocation and reclamation, on the other hand, take up more time and therefore have a greater impact on the overall performance of the program.
4 Use in standard libraries
4.1 fmt.Printf
The Go language standard library also makes extensive use of sync.Pool
, such as fmt
and encoding/json
.
The following is the source code for fmt.Printf
(go/src/fmt/print.go).
|
|
Calls to fmt.Printf
are very frequent. Using sync.Pool
to reuse pp objects can greatly improve performance, reduce memory usage, and reduce GC pressure.