Производительный, последовательный поиск в буфере на Go

Стоит задача: написать буфер (очередь), которая будет принимать поток байтов, производить разделение на фреймы и производить десериализацию при этом максимально эффективно (в плане производительности).

Вариант 1:

type nqueue struct {
    b   *bytes.Buffer
    len int
    cap int
}

func NewQueue(cap int) *nqueue {
    return &nqueue{
        b:     new(bytes.Buffer),
        len:   0,
        cap:   cap,
    }
}

func (q *nqueue) Queue(f *Frame) error {
    if q.len >= q.cap {
        return errors.New("queue overflow")
    }
    q.b.Write(f.Marshal())
    q.len++
    return nil
}

func (q *nqueue) Dequeue() (*Frame, error) {
    if q.len == 0 {
        return nil, errors.New("have no elements in queue")
    }
    q.len--
    preamble := []byte{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xD5}
    b := bytes.SplitN(q.b.Bytes(), preamble, 1)
    f, err := Unmarshal(b[0])
    if err != nil {
        return nil, err
    }
    return f, nil
}

Но тут есть один момент, нативно bytes.Buffer умеет разделять только по 1 байту в функции b.ReadBytes(delim), а разделения по цепочки байтов нету. В голову пришла идея через bytes.SplitN делить буфер по преамбуле и возвращать первый найденный элемент, но это дорого обходится.

Вариант 2, применяем трюк с буфером:

type Buffer struct {
    buf    []byte
    offset int
}

const smallestBufSize = 64
const smallestFrameSize = 46

var preamble = []byte{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xD5}

func (b *Buffer) grow(n int) int {
    sz := len(b.buf) - b.offset
    if sz == 0 && b.offset != 0 {
        b.buf = b.buf[:0]
        b.offset = 0
    }
    if b.buf == nil && n <= smallestBufSize {
        b.buf = make([]byte, smallestBufSize)
    }
    if i, ok := b.tryGrowByReslice(n); ok {
        return i
    }
    c := cap(b.buf)
    if n <= c/2-sz {
        copy(b.buf, b.buf[b.offset:])
    } else {
        buf := make([]byte, (c>>1)+n)
        copy(buf, b.buf[b.offset:])
        b.buf = buf
    }
    b.offset = 0
    b.buf = b.buf[:sz+n]
    return sz
}

func (b *Buffer) tryGrowByReslice(n int) (int, bool) {
    if l := len(b.buf); n <= cap(b.buf)-l {
        b.buf = b.buf[:l+n]
        return l, true
    }
    return 0, false
}

func (b *Buffer) Write(d []byte) (n int, err error) {
    sz := len(d)
    m, ok := b.tryGrowByReslice(sz)
    if !ok { // have no space
        m = b.grow(sz)
    }
    return copy(b.buf[m:], d), nil
}

func (b *Buffer) Read(p []byte) (n int, err error) {
    if len(b.buf) <= b.offset {
        return 0, io.EOF
    }
    n = copy(p, b.buf[b.offset:])
    b.offset += n
    return n, nil
}

func (b *Buffer) Next(n int) error {
    if len(b.buf) <= b.offset {
        return io.EOF
    }
    b.offset += n
    return nil
}

func (p *Buffer) ReadLast() ([]byte, error) {
    sz := len(p.buf)
    if sz <= smallestFrameSize {
        p.offset += sz
        return p.buf, nil
    }
    pos := bytes.Index(p.buf[len(preamble):], preamble)
    if pos < 0 {
        p.offset += sz
        return p.buf, nil
    }
    p.offset += pos
    return p.buf[:pos], nil
}

func (p *Buffer) ReadAndUnmarshal() (*ethernet.Frame, error) {
    b, err := p.ReadLast()
    if err != nil {
        return nil, err
    }
    return ethernet.Unmarshal(b)
}

Бенчмарк:

func BenchmarkQueue(b *testing.B) {
    q := NewQueue(b.N)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        f := NewFrame(BroadcastAddr, UnsetupedAddr, []byte{0xA})
        q.Send(f)
        f, err := q.Recv()
        if err != nil {
            panic(err)
        }
    }
}

func BenchmarkBuffer(b *testing.B) {
    buf := new(Buffer)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        f := NewFrame(BroadcastAddr, UnsetupedAddr, []byte{0xA})
        buf.Write(f.Marshal())
        f, err := buf.ReadAndUnmarshal()
        if err != nil {
            panic(err)
        }
    }
}

Результат бенчмарка: введите сюда описание изображения

Но я уверен это не предел мечтаний.


Ответы (0 шт):