Skip to content

Commit 2d56bda

Browse files
authored
mem: Remove Reader interface and export the concrete struct (#8669)
This PR changes the exported slice reader from an interface to a concrete struct. This approach follows the precedent set by standard library packages, such as [`bufio`'s `bufio.Reader`](https://pkg.go.dev/bufio#Reader). This interface was not intended for users to implement, and gRPC does not plan to provide alternative implementations. Users who require an interface for abstraction or testing can define one in their own packages. This change provides two main advantages: * Performance: It avoids a couple of heap allocations per stream that were previously required to hold the interface value. * Maintainability: Adding new methods to the concrete struct is a backward-compatible change, whereas adding methods to an interface is a breaking change. ## Benchmarks ```sh # test command $ go run benchmark/benchmain/main.go -benchtime=60s -workloads=unary \ -compression=off -maxConcurrentCalls=200 -trace=off \ -reqSizeBytes=100 -respSizeBytes=100 -networkMode=Local -resultFile="${RUN_NAME}" $ go run benchmark/benchresult/main.go unary-before unary-after Title Before After Percentage TotalOps 7801951 7883976 1.05% SendOps 0 0 NaN% RecvOps 0 0 NaN% Bytes/op 10005.90 9951.01 -0.54% Allocs/op 146.91 144.90 -1.36% ReqT/op 104026013.33 105119680.00 1.05% RespT/op 104026013.33 105119680.00 1.05% 50th-Lat 1.375183ms 1.359194ms -1.16% 90th-Lat 2.293816ms 2.258941ms -1.52% 99th-Lat 3.162307ms 3.157381ms -0.16% Avg-Lat 1.536462ms 1.520149ms -1.06% GoVersion go1.24.8 go1.24.8 GrpcVersion 1.77.0-dev 1.77.0-dev ``` RELEASE NOTES: * mem: Replace the `Reader` interface with a struct.
1 parent 8ab0c82 commit 2d56bda

File tree

2 files changed

+27
-34
lines changed

2 files changed

+27
-34
lines changed

internal/transport/controlbuf.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -665,11 +665,10 @@ func (l *loopyWriter) incomingSettingsHandler(s *incomingSettings) error {
665665

666666
func (l *loopyWriter) registerStreamHandler(h *registerStream) {
667667
str := &outStream{
668-
id: h.streamID,
669-
state: empty,
670-
itl: &itemList{},
671-
wq: h.wq,
672-
reader: mem.BufferSlice{}.Reader(),
668+
id: h.streamID,
669+
state: empty,
670+
itl: &itemList{},
671+
wq: h.wq,
673672
}
674673
l.estdStreams[h.streamID] = str
675674
}
@@ -701,11 +700,10 @@ func (l *loopyWriter) headerHandler(h *headerFrame) error {
701700
}
702701
// Case 2: Client wants to originate stream.
703702
str := &outStream{
704-
id: h.streamID,
705-
state: empty,
706-
itl: &itemList{},
707-
wq: h.wq,
708-
reader: mem.BufferSlice{}.Reader(),
703+
id: h.streamID,
704+
state: empty,
705+
itl: &itemList{},
706+
wq: h.wq,
709707
}
710708
return l.originateStream(str, h)
711709
}
@@ -948,11 +946,11 @@ func (l *loopyWriter) processData() (bool, error) {
948946
if str == nil {
949947
return true, nil
950948
}
951-
reader := str.reader
949+
reader := &str.reader
952950
dataItem := str.itl.peek().(*dataFrame) // Peek at the first data item this stream.
953951
if !dataItem.processing {
954952
dataItem.processing = true
955-
str.reader.Reset(dataItem.data)
953+
reader.Reset(dataItem.data)
956954
dataItem.data.Free()
957955
}
958956
// A data item is represented by a dataFrame, since it later translates into

mem/buffer_slice.go

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,9 @@ func (s BufferSlice) MaterializeToBuffer(pool BufferPool) Buffer {
117117

118118
// Reader returns a new Reader for the input slice after taking references to
119119
// each underlying buffer.
120-
func (s BufferSlice) Reader() Reader {
120+
func (s BufferSlice) Reader() *Reader {
121121
s.Ref()
122-
return &sliceReader{
122+
return &Reader{
123123
data: s,
124124
len: s.Len(),
125125
}
@@ -129,46 +129,40 @@ func (s BufferSlice) Reader() Reader {
129129
// with other parts systems. It also provides an additional convenience method
130130
// Remaining(), which returns the number of unread bytes remaining in the slice.
131131
// Buffers will be freed as they are read.
132-
type Reader interface {
133-
io.Reader
134-
io.ByteReader
135-
// Close frees the underlying BufferSlice and never returns an error. Subsequent
136-
// calls to Read will return (0, io.EOF).
137-
Close() error
138-
// Remaining returns the number of unread bytes remaining in the slice.
139-
Remaining() int
140-
// Reset frees the currently held buffer slice and starts reading from the
141-
// provided slice. This allows reusing the reader object.
142-
Reset(s BufferSlice)
143-
}
144-
145-
type sliceReader struct {
132+
// A Reader can be constructed from a BufferSlice; alternatively the zero value
133+
// of a Reader may be used after calling Reset on it.
134+
type Reader struct {
146135
data BufferSlice
147136
len int
148137
// The index into data[0].ReadOnlyData().
149138
bufferIdx int
150139
}
151140

152-
func (r *sliceReader) Remaining() int {
141+
// Remaining returns the number of unread bytes remaining in the slice.
142+
func (r *Reader) Remaining() int {
153143
return r.len
154144
}
155145

156-
func (r *sliceReader) Reset(s BufferSlice) {
146+
// Reset frees the currently held buffer slice and starts reading from the
147+
// provided slice. This allows reusing the reader object.
148+
func (r *Reader) Reset(s BufferSlice) {
157149
r.data.Free()
158150
s.Ref()
159151
r.data = s
160152
r.len = s.Len()
161153
r.bufferIdx = 0
162154
}
163155

164-
func (r *sliceReader) Close() error {
156+
// Close frees the underlying BufferSlice and never returns an error. Subsequent
157+
// calls to Read will return (0, io.EOF).
158+
func (r *Reader) Close() error {
165159
r.data.Free()
166160
r.data = nil
167161
r.len = 0
168162
return nil
169163
}
170164

171-
func (r *sliceReader) freeFirstBufferIfEmpty() bool {
165+
func (r *Reader) freeFirstBufferIfEmpty() bool {
172166
if len(r.data) == 0 || r.bufferIdx != len(r.data[0].ReadOnlyData()) {
173167
return false
174168
}
@@ -179,7 +173,7 @@ func (r *sliceReader) freeFirstBufferIfEmpty() bool {
179173
return true
180174
}
181175

182-
func (r *sliceReader) Read(buf []byte) (n int, _ error) {
176+
func (r *Reader) Read(buf []byte) (n int, _ error) {
183177
if r.len == 0 {
184178
return 0, io.EOF
185179
}
@@ -202,7 +196,8 @@ func (r *sliceReader) Read(buf []byte) (n int, _ error) {
202196
return n, nil
203197
}
204198

205-
func (r *sliceReader) ReadByte() (byte, error) {
199+
// ReadByte reads a single byte.
200+
func (r *Reader) ReadByte() (byte, error) {
206201
if r.len == 0 {
207202
return 0, io.EOF
208203
}

0 commit comments

Comments
 (0)