|
|
- package unsnap
-
- // copyright (c) 2014, Jason E. Aten
- // license: MIT
-
- // Some text from the Golang standard library doc is adapted and
- // reproduced in fragments below to document the expected behaviors
- // of the interface functions Read()/Write()/ReadFrom()/WriteTo() that
- // are implemented here. Those descriptions (see
- // http://golang.org/pkg/io/#Reader for example) are
- // copyright 2010 The Go Authors.
-
- import "io"
-
- // FixedSizeRingBuf:
- //
- // a fixed-size circular ring buffer. Yes, just what is says.
- //
- // We keep a pair of ping/pong buffers so that we can linearize
- // the circular buffer into a contiguous slice if need be.
- //
- // For efficiency, a FixedSizeRingBuf may be vastly preferred to
- // a bytes.Buffer. The ReadWithoutAdvance(), Advance(), and Adopt()
- // methods are all non-standard methods written for speed.
- //
- // For an I/O heavy application, I have replaced bytes.Buffer with
- // FixedSizeRingBuf and seen memory consumption go from 8GB to 25MB.
- // Yes, that is a 300x reduction in memory footprint. Everything ran
- // faster too.
- //
- // Note that Bytes(), while inescapable at times, is expensive: avoid
- // it if possible. Instead it is better to use the FixedSizeRingBuf.Readable
- // member to get the number of bytes available. Bytes() is expensive because
- // it may copy the back and then the front of a wrapped buffer A[Use]
- // into A[1-Use] in order to get a contiguous slice. If possible use ContigLen()
- // first to get the size that can be read without copying, Read() that
- // amount, and then Read() a second time -- to avoid the copy.
-
- type FixedSizeRingBuf struct {
- A [2][]byte // a pair of ping/pong buffers. Only one is active.
- Use int // which A buffer is in active use, 0 or 1
- N int // MaxViewInBytes, the size of A[0] and A[1] in bytes.
- Beg int // start of data in A[Use]
- Readable int // number of bytes available to read in A[Use]
-
- OneMade bool // lazily instantiate the [1] buffer. If we never call Bytes(),
- // we may never need it. If OneMade is false, the Use must be = 0.
- }
-
- func (b *FixedSizeRingBuf) Make2ndBuffer() {
- if b.OneMade {
- return
- }
- b.A[1] = make([]byte, b.N, b.N)
- b.OneMade = true
- }
-
- // get the length of the largest read that we can provide to a contiguous slice
- // without an extra linearizing copy of all bytes internally.
- func (b *FixedSizeRingBuf) ContigLen() int {
- extent := b.Beg + b.Readable
- firstContigLen := intMin(extent, b.N) - b.Beg
- return firstContigLen
- }
-
- func NewFixedSizeRingBuf(maxViewInBytes int) *FixedSizeRingBuf {
- n := maxViewInBytes
- r := &FixedSizeRingBuf{
- Use: 0, // 0 or 1, whichever is actually in use at the moment.
- // If we are asked for Bytes() and we wrap, linearize into the other.
-
- N: n,
- Beg: 0,
- Readable: 0,
- OneMade: false,
- }
- r.A[0] = make([]byte, n, n)
-
- // r.A[1] initialized lazily now.
-
- return r
- }
-
- // from the standard library description of Bytes():
- // Bytes() returns a slice of the contents of the unread portion of the buffer.
- // If the caller changes the contents of the
- // returned slice, the contents of the buffer will change provided there
- // are no intervening method calls on the Buffer.
- //
- func (b *FixedSizeRingBuf) Bytes() []byte {
-
- extent := b.Beg + b.Readable
- if extent <= b.N {
- // we fit contiguously in this buffer without wrapping to the other
- return b.A[b.Use][b.Beg:(b.Beg + b.Readable)]
- }
-
- // wrap into the other buffer
- b.Make2ndBuffer()
-
- src := b.Use
- dest := 1 - b.Use
-
- n := copy(b.A[dest], b.A[src][b.Beg:])
- n += copy(b.A[dest][n:], b.A[src][0:(extent%b.N)])
-
- b.Use = dest
- b.Beg = 0
-
- return b.A[b.Use][:n]
- }
-
- // Read():
- //
- // from bytes.Buffer.Read(): Read reads the next len(p) bytes
- // from the buffer or until the buffer is drained. The return
- // value n is the number of bytes read. If the buffer has no data
- // to return, err is io.EOF (unless len(p) is zero); otherwise it is nil.
- //
- // from the description of the Reader interface,
- // http://golang.org/pkg/io/#Reader
- //
- /*
- Reader is the interface that wraps the basic Read method.
-
- Read reads up to len(p) bytes into p. It returns the number
- of bytes read (0 <= n <= len(p)) and any error encountered.
- Even if Read returns n < len(p), it may use all of p as scratch
- space during the call. If some data is available but not
- len(p) bytes, Read conventionally returns what is available
- instead of waiting for more.
-
- When Read encounters an error or end-of-file condition after
- successfully reading n > 0 bytes, it returns the number of bytes
- read. It may return the (non-nil) error from the same call or
- return the error (and n == 0) from a subsequent call. An instance
- of this general case is that a Reader returning a non-zero number
- of bytes at the end of the input stream may return
- either err == EOF or err == nil. The next Read should
- return 0, EOF regardless.
-
- Callers should always process the n > 0 bytes returned before
- considering the error err. Doing so correctly handles I/O errors
- that happen after reading some bytes and also both of the
- allowed EOF behaviors.
-
- Implementations of Read are discouraged from returning a zero
- byte count with a nil error, and callers should treat that
- situation as a no-op.
- */
- //
-
- func (b *FixedSizeRingBuf) Read(p []byte) (n int, err error) {
- return b.ReadAndMaybeAdvance(p, true)
- }
-
- // if you want to Read the data and leave it in the buffer, so as
- // to peek ahead for example.
- func (b *FixedSizeRingBuf) ReadWithoutAdvance(p []byte) (n int, err error) {
- return b.ReadAndMaybeAdvance(p, false)
- }
-
- func (b *FixedSizeRingBuf) ReadAndMaybeAdvance(p []byte, doAdvance bool) (n int, err error) {
- if len(p) == 0 {
- return 0, nil
- }
- if b.Readable == 0 {
- return 0, io.EOF
- }
- extent := b.Beg + b.Readable
- if extent <= b.N {
- n += copy(p, b.A[b.Use][b.Beg:extent])
- } else {
- n += copy(p, b.A[b.Use][b.Beg:b.N])
- if n < len(p) {
- n += copy(p[n:], b.A[b.Use][0:(extent%b.N)])
- }
- }
- if doAdvance {
- b.Advance(n)
- }
- return
- }
-
- //
- // Write writes len(p) bytes from p to the underlying data stream.
- // It returns the number of bytes written from p (0 <= n <= len(p))
- // and any error encountered that caused the write to stop early.
- // Write must return a non-nil error if it returns n < len(p).
- //
- func (b *FixedSizeRingBuf) Write(p []byte) (n int, err error) {
- for {
- if len(p) == 0 {
- // nothing (left) to copy in; notice we shorten our
- // local copy p (below) as we read from it.
- return
- }
-
- writeCapacity := b.N - b.Readable
- if writeCapacity <= 0 {
- // we are all full up already.
- return n, io.ErrShortWrite
- }
- if len(p) > writeCapacity {
- err = io.ErrShortWrite
- // leave err set and
- // keep going, write what we can.
- }
-
- writeStart := (b.Beg + b.Readable) % b.N
-
- upperLim := intMin(writeStart+writeCapacity, b.N)
-
- k := copy(b.A[b.Use][writeStart:upperLim], p)
-
- n += k
- b.Readable += k
- p = p[k:]
-
- // we can fill from b.A[b.Use][0:something] from
- // p's remainder, so loop
- }
- }
-
- // WriteTo and ReadFrom avoid intermediate allocation and copies.
-
- // WriteTo writes data to w until there's no more data to write
- // or when an error occurs. The return value n is the number of
- // bytes written. Any error encountered during the write is also returned.
- func (b *FixedSizeRingBuf) WriteTo(w io.Writer) (n int64, err error) {
-
- if b.Readable == 0 {
- return 0, io.EOF
- }
-
- extent := b.Beg + b.Readable
- firstWriteLen := intMin(extent, b.N) - b.Beg
- secondWriteLen := b.Readable - firstWriteLen
- if firstWriteLen > 0 {
- m, e := w.Write(b.A[b.Use][b.Beg:(b.Beg + firstWriteLen)])
- n += int64(m)
- b.Advance(m)
-
- if e != nil {
- return n, e
- }
- // all bytes should have been written, by definition of
- // Write method in io.Writer
- if m != firstWriteLen {
- return n, io.ErrShortWrite
- }
- }
- if secondWriteLen > 0 {
- m, e := w.Write(b.A[b.Use][0:secondWriteLen])
- n += int64(m)
- b.Advance(m)
-
- if e != nil {
- return n, e
- }
- // all bytes should have been written, by definition of
- // Write method in io.Writer
- if m != secondWriteLen {
- return n, io.ErrShortWrite
- }
- }
-
- return n, nil
- }
-
- // ReadFrom() reads data from r until EOF or error. The return value n
- // is the number of bytes read. Any error except io.EOF encountered
- // during the read is also returned.
- func (b *FixedSizeRingBuf) ReadFrom(r io.Reader) (n int64, err error) {
- for {
- writeCapacity := b.N - b.Readable
- if writeCapacity <= 0 {
- // we are all full
- return n, nil
- }
- writeStart := (b.Beg + b.Readable) % b.N
- upperLim := intMin(writeStart+writeCapacity, b.N)
-
- m, e := r.Read(b.A[b.Use][writeStart:upperLim])
- n += int64(m)
- b.Readable += m
- if e == io.EOF {
- return n, nil
- }
- if e != nil {
- return n, e
- }
- }
- }
-
- func (b *FixedSizeRingBuf) Reset() {
- b.Beg = 0
- b.Readable = 0
- b.Use = 0
- }
-
- // Advance(): non-standard, but better than Next(),
- // because we don't have to unwrap our buffer and pay the cpu time
- // for the copy that unwrapping may need.
- // Useful in conjuction/after ReadWithoutAdvance() above.
- func (b *FixedSizeRingBuf) Advance(n int) {
- if n <= 0 {
- return
- }
- if n > b.Readable {
- n = b.Readable
- }
- b.Readable -= n
- b.Beg = (b.Beg + n) % b.N
- }
-
- // Adopt(): non-standard.
- //
- // For efficiency's sake, (possibly) take ownership of
- // already allocated slice offered in me.
- //
- // If me is large we will adopt it, and we will potentially then
- // write to the me buffer.
- // If we already have a bigger buffer, copy me into the existing
- // buffer instead.
- func (b *FixedSizeRingBuf) Adopt(me []byte) {
- n := len(me)
- if n > b.N {
- b.A[0] = me
- b.OneMade = false
- b.N = n
- b.Use = 0
- b.Beg = 0
- b.Readable = n
- } else {
- // we already have a larger buffer, reuse it.
- copy(b.A[0], me)
- b.Use = 0
- b.Beg = 0
- b.Readable = n
- }
- }
-
- func intMax(a, b int) int {
- if a > b {
- return a
- } else {
- return b
- }
- }
-
- func intMin(a, b int) int {
- if a < b {
- return a
- } else {
- return b
- }
- }
-
- // Get the (beg, end] indices of the tailing empty buffer of bytes slice that from that is free for writing.
- // Note: not guaranteed to be zeroed. At all.
- func (b *FixedSizeRingBuf) GetEndmostWritable() (beg int, end int) {
- extent := b.Beg + b.Readable
- if extent < b.N {
- return extent, b.N
- }
-
- return extent % b.N, b.Beg
- }
-
- // Note: not guaranteed to be zeroed.
- func (b *FixedSizeRingBuf) GetEndmostWritableSlice() []byte {
- beg, e := b.GetEndmostWritable()
- return b.A[b.Use][beg:e]
- }
|