diff --git a/bench_test.go b/bench_test.go index 39fdb7c..de25331 100644 --- a/bench_test.go +++ b/bench_test.go @@ -54,6 +54,7 @@ func BenchmarkSendRecv(b *testing.B) { recvBuf := make([]byte, 512) doneCh := make(chan struct{}) + b.ResetTimer() go func() { defer close(doneCh) defer server.Close() diff --git a/util.go b/util.go index 177eb98..aeccd24 100644 --- a/util.go +++ b/util.go @@ -68,15 +68,21 @@ type segmentedBuffer struct { cap uint32 len uint32 bm sync.Mutex - // read position in b[0]. + // read position in b[bPos]. // We must not reslice any of the buffers in b, as we need to put them back into the pool. readPos int - b [][]byte + // bPos is an index in b slice. If bPos == len(b), it means that buffer is empty. + bPos int + // b is used as a growable buffer. Each Append adds []byte to the end of b. + // If there is no space available at the end of the buffer (len(b) == cap(b)), but it has space + // at the beginning (bPos > 0 and at least 1/4 of the buffer is empty), data inside b is shifted to the beginning. + // Each Read reads from b[bPos] and increments bPos if b[bPos] was fully read. + b [][]byte } // NewSegmentedBuffer allocates a ring buffer. func newSegmentedBuffer(initialCapacity uint32) segmentedBuffer { - return segmentedBuffer{cap: initialCapacity, b: make([][]byte, 0)} + return segmentedBuffer{cap: initialCapacity, b: make([][]byte, 0, 16)} } // Len is the amount of data in the receive buffer. @@ -109,15 +115,15 @@ func (s *segmentedBuffer) GrowTo(max uint32, force bool) (bool, uint32) { func (s *segmentedBuffer) Read(b []byte) (int, error) { s.bm.Lock() defer s.bm.Unlock() - if len(s.b) == 0 { + if s.bPos == len(s.b) { return 0, io.EOF } - data := s.b[0][s.readPos:] + data := s.b[s.bPos][s.readPos:] n := copy(b, data) if n == len(data) { - pool.Put(s.b[0]) - s.b[0] = nil - s.b = s.b[1:] + pool.Put(s.b[s.bPos]) + s.b[s.bPos] = nil + s.bPos++ s.readPos = 0 } else { s.readPos += n @@ -152,6 +158,23 @@ func (s *segmentedBuffer) Append(input io.Reader, length uint32) error { if n > 0 { s.len += uint32(n) s.cap -= uint32(n) + // s.b has no available space at the end, but has space at the beginning + if len(s.b) == cap(s.b) && s.bPos > 0 { + if s.bPos == len(s.b) { + // the buffer is empty, so just move pos + s.bPos = 0 + s.b = s.b[:0] + } else if s.bPos > cap(s.b)/4 { + // at least 1/4 of buffer is empty, so shift data to the left to free space at the end + copied := copy(s.b, s.b[s.bPos:]) + // clear references to copied data + for i := copied; i < len(s.b); i++ { + s.b[i] = nil + } + s.b = s.b[:copied] + s.bPos = 0 + } + } s.b = append(s.b, dst[0:n]) } return err