mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-10-19 07:55:07 +00:00
183 lines
5.9 KiB
Go
183 lines
5.9 KiB
Go
|
// Copyright 2020 The Go Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package json
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"io"
|
||
|
"math/bits"
|
||
|
"sort"
|
||
|
"sync"
|
||
|
)
|
||
|
|
||
|
// TODO(https://go.dev/issue/47657): Use sync.PoolOf.
|
||
|
|
||
|
var (
|
||
|
// This owns the internal buffer since there is no io.Writer to output to.
|
||
|
// Since the buffer can get arbitrarily large in normal usage,
|
||
|
// there is statistical tracking logic to determine whether to recycle
|
||
|
// the internal buffer or not based on a history of utilization.
|
||
|
bufferedEncoderPool = &sync.Pool{New: func() any { return new(Encoder) }}
|
||
|
|
||
|
// This owns the internal buffer, but it is only used to temporarily store
|
||
|
// buffered JSON before flushing it to the underlying io.Writer.
|
||
|
// In a sufficiently efficient streaming mode, we do not expect the buffer
|
||
|
// to grow arbitrarily large. Thus, we avoid recycling large buffers.
|
||
|
streamingEncoderPool = &sync.Pool{New: func() any { return new(Encoder) }}
|
||
|
|
||
|
// This does not own the internal buffer since
|
||
|
// it is taken directly from the provided bytes.Buffer.
|
||
|
bytesBufferEncoderPool = &sync.Pool{New: func() any { return new(Encoder) }}
|
||
|
)
|
||
|
|
||
|
// bufferStatistics is statistics to track buffer utilization.
|
||
|
// It is used to determine whether to recycle a buffer or not
|
||
|
// to avoid https://go.dev/issue/23199.
|
||
|
type bufferStatistics struct {
|
||
|
strikes int // number of times the buffer was under-utilized
|
||
|
prevLen int // length of previous buffer
|
||
|
}
|
||
|
|
||
|
func getBufferedEncoder(o EncodeOptions) *Encoder {
|
||
|
e := bufferedEncoderPool.Get().(*Encoder)
|
||
|
if e.buf == nil {
|
||
|
// Round up to nearest 2ⁿ to make best use of malloc size classes.
|
||
|
// See runtime/sizeclasses.go on Go1.15.
|
||
|
// Logical OR with 63 to ensure 64 as the minimum buffer size.
|
||
|
n := 1 << bits.Len(uint(e.bufStats.prevLen|63))
|
||
|
e.buf = make([]byte, 0, n)
|
||
|
}
|
||
|
e.reset(e.buf[:0], nil, o)
|
||
|
return e
|
||
|
}
|
||
|
func putBufferedEncoder(e *Encoder) {
|
||
|
// 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
|
||
|
// won't be kept alive by a continuous stream of small usages.
|
||
|
//
|
||
|
// The worst case utilization is computed as:
|
||
|
// MIN_UTILIZATION_THRESHOLD / (1 + MAX_NUM_STRIKES)
|
||
|
//
|
||
|
// For the constants chosen below, this is (25%)/(1+4) ⇒ 5%.
|
||
|
// This may seem low, but it ensures a lower bound on
|
||
|
// the absolute worst-case utilization. Without this check,
|
||
|
// this would be theoretically 0%, which is infinitely worse.
|
||
|
//
|
||
|
// See https://go.dev/issue/27735.
|
||
|
switch {
|
||
|
case cap(e.buf) <= 4<<10: // always recycle buffers smaller than 4KiB
|
||
|
e.bufStats.strikes = 0
|
||
|
case cap(e.buf)/4 <= len(e.buf): // at least 25% utilization
|
||
|
e.bufStats.strikes = 0
|
||
|
case e.bufStats.strikes < 4: // at most 4 strikes
|
||
|
e.bufStats.strikes++
|
||
|
default: // discard the buffer; too large and too often under-utilized
|
||
|
e.bufStats.strikes = 0
|
||
|
e.bufStats.prevLen = len(e.buf) // heuristic for size to allocate next time
|
||
|
e.buf = nil
|
||
|
}
|
||
|
bufferedEncoderPool.Put(e)
|
||
|
}
|
||
|
|
||
|
func getStreamingEncoder(w io.Writer, o EncodeOptions) *Encoder {
|
||
|
if _, ok := w.(*bytes.Buffer); ok {
|
||
|
e := bytesBufferEncoderPool.Get().(*Encoder)
|
||
|
e.reset(nil, w, o) // buffer taken from bytes.Buffer
|
||
|
return e
|
||
|
} else {
|
||
|
e := streamingEncoderPool.Get().(*Encoder)
|
||
|
e.reset(e.buf[:0], w, o) // preserve existing buffer
|
||
|
return e
|
||
|
}
|
||
|
}
|
||
|
func putStreamingEncoder(e *Encoder) {
|
||
|
if _, ok := e.wr.(*bytes.Buffer); ok {
|
||
|
bytesBufferEncoderPool.Put(e)
|
||
|
} else {
|
||
|
if cap(e.buf) > 64<<10 {
|
||
|
e.buf = nil // avoid pinning arbitrarily large amounts of memory
|
||
|
}
|
||
|
streamingEncoderPool.Put(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
// This does not own the internal buffer since it is externally provided.
|
||
|
bufferedDecoderPool = &sync.Pool{New: func() any { return new(Decoder) }}
|
||
|
|
||
|
// This owns the internal buffer, but it is only used to temporarily store
|
||
|
// buffered JSON fetched from the underlying io.Reader.
|
||
|
// In a sufficiently efficient streaming mode, we do not expect the buffer
|
||
|
// to grow arbitrarily large. Thus, we avoid recycling large buffers.
|
||
|
streamingDecoderPool = &sync.Pool{New: func() any { return new(Decoder) }}
|
||
|
|
||
|
// This does not own the internal buffer since
|
||
|
// it is taken directly from the provided bytes.Buffer.
|
||
|
bytesBufferDecoderPool = bufferedDecoderPool
|
||
|
)
|
||
|
|
||
|
func getBufferedDecoder(b []byte, o DecodeOptions) *Decoder {
|
||
|
d := bufferedDecoderPool.Get().(*Decoder)
|
||
|
d.reset(b, nil, o)
|
||
|
return d
|
||
|
}
|
||
|
func putBufferedDecoder(d *Decoder) {
|
||
|
bufferedDecoderPool.Put(d)
|
||
|
}
|
||
|
|
||
|
func getStreamingDecoder(r io.Reader, o DecodeOptions) *Decoder {
|
||
|
if _, ok := r.(*bytes.Buffer); ok {
|
||
|
d := bytesBufferDecoderPool.Get().(*Decoder)
|
||
|
d.reset(nil, r, o) // buffer taken from bytes.Buffer
|
||
|
return d
|
||
|
} else {
|
||
|
d := streamingDecoderPool.Get().(*Decoder)
|
||
|
d.reset(d.buf[:0], r, o) // preserve existing buffer
|
||
|
return d
|
||
|
}
|
||
|
}
|
||
|
func putStreamingDecoder(d *Decoder) {
|
||
|
if _, ok := d.rd.(*bytes.Buffer); ok {
|
||
|
bytesBufferDecoderPool.Put(d)
|
||
|
} else {
|
||
|
if cap(d.buf) > 64<<10 {
|
||
|
d.buf = nil // avoid pinning arbitrarily large amounts of memory
|
||
|
}
|
||
|
streamingDecoderPool.Put(d)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var stringsPools = &sync.Pool{New: func() any { return new(stringSlice) }}
|
||
|
|
||
|
type stringSlice []string
|
||
|
|
||
|
// getStrings returns a non-nil pointer to a slice with length n.
|
||
|
func getStrings(n int) *stringSlice {
|
||
|
s := stringsPools.Get().(*stringSlice)
|
||
|
if cap(*s) < n {
|
||
|
*s = make([]string, n)
|
||
|
}
|
||
|
*s = (*s)[:n]
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
func putStrings(s *stringSlice) {
|
||
|
if cap(*s) > 1<<10 {
|
||
|
*s = nil // avoid pinning arbitrarily large amounts of memory
|
||
|
}
|
||
|
stringsPools.Put(s)
|
||
|
}
|
||
|
|
||
|
// Sort sorts the string slice according to RFC 8785, section 3.2.3.
|
||
|
func (ss *stringSlice) Sort() {
|
||
|
// TODO(https://go.dev/issue/47619): Use slices.SortFunc instead.
|
||
|
sort.Sort(ss)
|
||
|
}
|
||
|
|
||
|
func (ss *stringSlice) Len() int { return len(*ss) }
|
||
|
func (ss *stringSlice) Less(i, j int) bool { return lessUTF16((*ss)[i], (*ss)[j]) }
|
||
|
func (ss *stringSlice) Swap(i, j int) { (*ss)[i], (*ss)[j] = (*ss)[j], (*ss)[i] }
|