perf(history): pool zlib writers for idle page compression
Each BestSpeed writer allocates ~600 KiB of deflate state. Reuse them across pages instead of allocating one per compressed page. BenchmarkColdHistoryRetained10Kx120/plain-ascii: 66.3 -> 37.0 MB/op
- commit
- 92e0c365a3fe589fac04fa54cfc4d0d64a0b33fb
- author
- brice · · 2026-10-07 16:24 UTC
- parent
- adadad1
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +19 -5
M history_storage.go +16 -5
| @@ -113,6 +113,16 @@ func (c *HistoryChunk) Restore() error { | |||
| 113 | 113 | return err | |
| 114 | 114 | } | |
| 115 | 115 | ||
| 116 | + | // pageCompressorPool reuses BestSpeed writers; each one owns ~600 KiB of | |
| 117 | + | // deflate state that would otherwise be allocated per compressed page. | |
| 118 | + | var pageCompressorPool = sync.Pool{New: func() any { | |
| 119 | + | w, err := zlib.NewWriterLevel(io.Discard, zlib.BestSpeed) | |
| 120 | + | if err != nil { | |
| 121 | + | panic(err) // BestSpeed is a valid level | |
| 122 | + | } | |
| 123 | + | return w | |
| 124 | + | }} | |
| 125 | + | ||
| 116 | 126 | func (p *sealedPage) compressIfIdle() (bool, error) { | |
| 117 | 127 | p.mu.Lock() | |
| 118 | 128 | defer p.mu.Unlock() | |
| @@ -143,12 +153,13 @@ func (p *sealedPage) compressIfIdle() (bool, error) { | |||
| 143 | 153 | return false, errors.New("history page exceeds encoded backing limit") | |
| 144 | 154 | } | |
| 145 | 155 | var out bytes.Buffer | |
| 146 | - | w, err := zlib.NewWriterLevel(&out, zlib.BestSpeed) | |
| 147 | - | if err != nil { | |
| 148 | - | return false, err | |
| 149 | - | } | |
| 156 | + | w := pageCompressorPool.Get().(*zlib.Writer) | |
| 157 | + | w.Reset(&out) | |
| 158 | + | defer func() { | |
| 159 | + | w.Reset(io.Discard) | |
| 160 | + | pageCompressorPool.Put(w) | |
| 161 | + | }() | |
| 150 | 162 | if _, err := w.Write(raw); err != nil { | |
| 151 | - | _ = w.Close() | |
| 152 | 163 | return false, err | |
| 153 | 164 | } | |
| 154 | 165 | if err := w.Close(); err != nil { | |
| @@ -113,6 +113,16 @@ func (c *HistoryChunk) Restore() error { | |||||
| 113 | return err | 113 | return err | ||
| 114 | } | 114 | } | ||
| 115 | 115 | ||||
| 116 | + | // pageCompressorPool reuses BestSpeed writers; each one owns ~600 KiB of | |||
| 117 | + | // deflate state that would otherwise be allocated per compressed page. | |||
| 118 | + | var pageCompressorPool = sync.Pool{New: func() any { | |||
| 119 | + | w, err := zlib.NewWriterLevel(io.Discard, zlib.BestSpeed) | |||
| 120 | + | if err != nil { | |||
| 121 | + | panic(err) // BestSpeed is a valid level | |||
| 122 | + | } | |||
| 123 | + | return w | |||
| 124 | + | }} | |||
| 125 | + | ||||
| 116 | func (p *sealedPage) compressIfIdle() (bool, error) { | 126 | func (p *sealedPage) compressIfIdle() (bool, error) { | ||
| 117 | p.mu.Lock() | 127 | p.mu.Lock() | ||
| 118 | defer p.mu.Unlock() | 128 | defer p.mu.Unlock() | ||
| @@ -143,12 +153,13 @@ func (p *sealedPage) compressIfIdle() (bool, error) { | |||||
| 143 | return false, errors.New("history page exceeds encoded backing limit") | 153 | return false, errors.New("history page exceeds encoded backing limit") | ||
| 144 | } | 154 | } | ||
| 145 | var out bytes.Buffer | 155 | var out bytes.Buffer | ||
| 146 | - | w, err := zlib.NewWriterLevel(&out, zlib.BestSpeed) | 156 | + | w := pageCompressorPool.Get().(*zlib.Writer) |
| 147 | - | if err != nil { | 157 | + | w.Reset(&out) |
| 148 | - | return false, err | 158 | + | defer func() { |
| 149 | - | } | 159 | + | w.Reset(io.Discard) |
| 160 | + | pageCompressorPool.Put(w) | |||
| 161 | + | }() | |||
| 150 | if _, err := w.Write(raw); err != nil { | 162 | if _, err := w.Write(raw); err != nil { | ||
| 151 | - | _ = w.Close() | |||
| 152 | return false, err | 163 | return false, err | ||
| 153 | } | 164 | } | ||
| 154 | if err := w.Close(); err != nil { | 165 | if err := w.Close(); err != nil { | ||
M storage_benchmark_test.go +3 -0
| @@ -78,6 +78,9 @@ func BenchmarkColdHistoryRetained10Kx120(b *testing.B) { | |||
| 78 | 78 | } | |
| 79 | 79 | } | |
| 80 | 80 | benchmarkStorageHistorySink = h | |
| 81 | + | // Two cycles drain the compressor sync.Pool, so only the | |
| 82 | + | // history itself is measured. | |
| 83 | + | runtime.GC() | |
| 81 | 84 | runtime.GC() | |
| 82 | 85 | runtime.ReadMemStats(&after) | |
| 83 | 86 | if after.HeapAlloc >= before.HeapAlloc { | |
| @@ -78,6 +78,9 @@ func BenchmarkColdHistoryRetained10Kx120(b *testing.B) { | |||||
| 78 | } | 78 | } | ||
| 79 | } | 79 | } | ||
| 80 | benchmarkStorageHistorySink = h | 80 | benchmarkStorageHistorySink = h | ||
| 81 | + | // Two cycles drain the compressor sync.Pool, so only the | |||
| 82 | + | // history itself is measured. | |||
| 83 | + | runtime.GC() | |||
| 81 | runtime.GC() | 84 | runtime.GC() | ||
| 82 | runtime.ReadMemStats(&after) | 85 | runtime.ReadMemStats(&after) | ||
| 83 | if after.HeapAlloc >= before.HeapAlloc { | 86 | if after.HeapAlloc >= before.HeapAlloc { | ||