]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/json/jsontext: avoid pinning application data in pools
authorJoe Tsai <joetsai@digital-static.net>
Tue, 21 Oct 2025 22:11:32 +0000 (15:11 -0700)
committerGopher Robot <gobot@golang.org>
Fri, 24 Oct 2025 16:07:03 +0000 (09:07 -0700)
Previously, we put a jsontext.Encoder (or Decoder)
back into a pool with minimal reset logic.
This was semantically safe since we always did a full reset
after obtaining an Encoder/Decoder back out of the pool.

However, this meant that so long as an Encoder/Decoder was
alive in the pool, any application data referenced by the coder
would be kept alive longer than necessary.
Explicitly, clear such fields so that application data
can be more aggressively garbage collected.

Performance:

name               old time/op    new time/op    delta
Unmarshal/Bool-32    52.0ns ± 3%    50.3ns ± 3%  -3.30%  (p=0.001 n=10+10)
Marshal/Bool-32      55.4ns ± 3%    54.4ns ± 2%  -1.75%  (p=0.006 n=10+9)

This only impacts the performance of discrete Marshal/Unmarshal calls.
For the simplest possible call (i.e., to marsha/unmarshal a bool),
there is a 1-2ns slow down. This is an appropriate slowdown
for an improvement in memory utilization.

Change-Id: I5e7d7827473773e53a9dcb3d7fe9052a75481e9f
Reviewed-on: https://go-review.googlesource.com/c/go/+/713640
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
TryBot-Bypass: Damien Neil <dneil@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Auto-Submit: Damien Neil <dneil@google.com>

src/encoding/json/jsontext/pools.go
src/encoding/json/v2/arshal.go

index 4f9e0ea410ccd2f47bc9639a548ea2c7536db596..3066ab4a1d2590838738c9edfeef541d127cd503 100644 (file)
@@ -54,6 +54,10 @@ func getBufferedEncoder(opts ...Options) *Encoder {
        return e
 }
 func putBufferedEncoder(e *Encoder) {
+       if cap(e.s.availBuffer) > 64<<10 {
+               e.s.availBuffer = nil // avoid pinning arbitrarily large amounts of memory
+       }
+
        // Recycle large buffers only if sufficiently utilized.
        // If a buffer is under-utilized enough times sequentially,
        // then it is discarded, ensuring that a single large buffer
@@ -95,9 +99,14 @@ func getStreamingEncoder(w io.Writer, opts ...Options) *Encoder {
        }
 }
 func putStreamingEncoder(e *Encoder) {
+       if cap(e.s.availBuffer) > 64<<10 {
+               e.s.availBuffer = nil // avoid pinning arbitrarily large amounts of memory
+       }
        if _, ok := e.s.wr.(*bytes.Buffer); ok {
+               e.s.wr, e.s.Buf = nil, nil // avoid pinning the provided bytes.Buffer
                bytesBufferEncoderPool.Put(e)
        } else {
+               e.s.wr = nil // avoid pinning the provided io.Writer
                if cap(e.s.Buf) > 64<<10 {
                        e.s.Buf = nil // avoid pinning arbitrarily large amounts of memory
                }
@@ -126,6 +135,7 @@ func getBufferedDecoder(b []byte, opts ...Options) *Decoder {
        return d
 }
 func putBufferedDecoder(d *Decoder) {
+       d.s.buf = nil // avoid pinning the provided buffer
        bufferedDecoderPool.Put(d)
 }
 
@@ -142,8 +152,10 @@ func getStreamingDecoder(r io.Reader, opts ...Options) *Decoder {
 }
 func putStreamingDecoder(d *Decoder) {
        if _, ok := d.s.rd.(*bytes.Buffer); ok {
+               d.s.rd, d.s.buf = nil, nil // avoid pinning the provided bytes.Buffer
                bytesBufferDecoderPool.Put(d)
        } else {
+               d.s.rd = nil // avoid pinning the provided io.Reader
                if cap(d.s.buf) > 64<<10 {
                        d.s.buf = nil // avoid pinning arbitrarily large amounts of memory
                }
index e26f3340ee2f3fad5be32afdf51cf6a92177549e..5537a467d836e3cfe86aa583a648e4bc5ca7b7b6 100644 (file)
@@ -571,5 +571,6 @@ func putStrings(s *stringSlice) {
        if cap(*s) > 1<<10 {
                *s = nil // avoid pinning arbitrarily large amounts of memory
        }
+       clear(*s) // avoid pinning a reference to each string
        stringsPools.Put(s)
 }