Производительный, последовательный поиск в буфере на 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)
}
}
}
Но я уверен это не предел мечтаний.
