Skip to content

Commit

Permalink
feat: use slab allocation for strings and bytes (#255)
Browse files Browse the repository at this point in the history
  • Loading branch information
nrwiersma authored May 28, 2023
1 parent 10253c8 commit ccbe008
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 25 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,14 @@ At this moment recursive structs are not supported. It is planned for the future
Benchmark source code can be found at: [https://github.com/nrwiersma/avro-benchmarks](https://github.com/nrwiersma/avro-benchmarks)

```
BenchmarkGoAvroDecode-10 495176 2413 ns/op 418 B/op 27 allocs/op
BenchmarkGoAvroEncode-10 420168 2917 ns/op 948 B/op 63 allocs/op
BenchmarkGoGenAvroDecode-10 757150 1552 ns/op 728 B/op 45 allocs/op
BenchmarkGoGenAvroEncode-10 1882940 639.0 ns/op 256 B/op 3 allocs/op
BenchmarkHambaDecode-10 3138063 383.0 ns/op 64 B/op 4 allocs/op
BenchmarkHambaEncode-10 4377513 273.3 ns/op 112 B/op 1 allocs/op
BenchmarkLinkedinDecode-10 1000000 1109 ns/op 1688 B/op 35 allocs/op
BenchmarkLinkedinEncode-10 2641016 456.0 ns/op 248 B/op 5 allocs/op
BenchmarkGoAvroDecode-8 788455 1505 ns/op 418 B/op 27 allocs/op
BenchmarkGoAvroEncode-8 624343 1908 ns/op 806 B/op 63 allocs/op
BenchmarkGoGenAvroDecode-8 1360375 876.4 ns/op 320 B/op 11 allocs/op
BenchmarkGoGenAvroEncode-8 2801583 425.9 ns/op 240 B/op 3 allocs/op
BenchmarkHambaDecode-8 5046832 238.7 ns/op 47 B/op 0 allocs/op
BenchmarkHambaEncode-8 6017635 196.2 ns/op 112 B/op 1 allocs/op
BenchmarkLinkedinDecode-8 1000000 1003 ns/op 1688 B/op 35 allocs/op
BenchmarkLinkedinEncode-8 3170553 381.5 ns/op 248 B/op 5 allocs/op
```

Always benchmark with your own workload. The result depends heavily on the data input.
Expand Down
1 change: 0 additions & 1 deletion decoder_fixed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ func TestDecoder_FixedLogicalDurationSizeNot12(t *testing.T) {
dec, err := avro.NewDecoder(schema, bytes.NewReader(data))
require.NoError(t, err)

fmt.Printf("decoder: starting test\n")
got := avro.LogicalDuration{}
err = dec.Decode(&got)
assert.Error(t, err)
Expand Down
43 changes: 27 additions & 16 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func WithReaderConfig(cfg API) ReaderFunc {
type Reader struct {
cfg *frozenConfig
reader io.Reader
slab []byte
buf []byte
head int
tail int
Expand Down Expand Up @@ -199,35 +200,45 @@ func (r *Reader) ReadDouble() float64 {

// ReadBytes reads Bytes from the Reader.
func (r *Reader) ReadBytes() []byte {
size := r.ReadLong()
if size < 0 {
r.ReportError("ReadBytes", "invalid bytes length")
return nil
}

buf := make([]byte, size)
r.Read(buf)
return buf
return r.readBytes("bytes")
}

// ReadString reads a String from the Reader.
func (r *Reader) ReadString() string {
b := r.readBytes("string")
if len(b) == 0 {
return ""
}

return *(*string)(unsafe.Pointer(&b))
}

func (r *Reader) readBytes(op string) []byte {
size := int(r.ReadLong())
if size < 0 {
r.ReportError("ReadString", "invalid string length")
return ""
r.ReportError("ReadString", "invalid "+op+" length")
return nil
}
if size == 0 {
return []byte{}
}

// The string is entirely in the current buffer, fast path.
if r.head+size <= r.tail {
ret := string(r.buf[r.head : r.head+size])
// The bytes are entirely in the buffer and of a reasonable size.
// Use the byte slab.
if r.head+size <= r.tail && size <= 1024 {
if cap(r.slab) < size {
r.slab = make([]byte, 1024)
}
dst := r.slab[:size]
r.slab = r.slab[size:]
copy(dst, r.buf[r.head:r.head+size])
r.head += size
return ret
return dst
}

buf := make([]byte, size)
r.Read(buf)
return *(*string)(unsafe.Pointer(&buf))
return buf
}

// ReadBlockHeader reads a Block Header from the Reader.
Expand Down

0 comments on commit ccbe008

Please sign in to comment.