-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
decoders: replace binary.Read with a version without reflection and a…
…llocations (#141) Instead of allocating small slices, we rely on the fact that most call sites are providing a `bytes.Buffer` and use the `Next()` method. For sFlow decoding, in my case, we get a 33% speedup. A `bytes.Reader` would even be more efficient, but unfortunately, they don't have a `Next()` method. While Go should be smart enough to make the allocation of `bs` on the stack, it does not, even when `io.ReadFull()` is inlines. ``` decoders/utils/utils.go:23:13: make([]byte, n) escapes to heap ```
- Loading branch information
1 parent
7c52d81
commit 66b47ee
Showing
6 changed files
with
338 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,128 @@ | ||
package utils | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"errors" | ||
"io" | ||
"reflect" | ||
) | ||
|
||
func BinaryDecoder(payload io.Reader, dests ...interface{}) error { | ||
type BytesBuffer interface { | ||
io.Reader | ||
Next(int) []byte | ||
} | ||
|
||
func BinaryDecoder(payload *bytes.Buffer, dests ...interface{}) error { | ||
for _, dest := range dests { | ||
err := binary.Read(payload, binary.BigEndian, dest) | ||
err := BinaryRead(payload, binary.BigEndian, dest) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
func BinaryRead(payload BytesBuffer, order binary.ByteOrder, data any) error { | ||
// Fast path for basic types and slices. | ||
if n := intDataSize(data); n != 0 { | ||
bs := payload.Next(n) | ||
if len(bs) < n { | ||
return io.ErrUnexpectedEOF | ||
} | ||
switch data := data.(type) { | ||
case *bool: | ||
*data = bs[0] != 0 | ||
case *int8: | ||
*data = int8(bs[0]) | ||
case *uint8: | ||
*data = bs[0] | ||
case *int16: | ||
*data = int16(order.Uint16(bs)) | ||
case *uint16: | ||
*data = order.Uint16(bs) | ||
case *int32: | ||
*data = int32(order.Uint32(bs)) | ||
case *uint32: | ||
*data = order.Uint32(bs) | ||
case *int64: | ||
*data = int64(order.Uint64(bs)) | ||
case *uint64: | ||
*data = order.Uint64(bs) | ||
case []bool: | ||
for i, x := range bs { // Easier to loop over the input for 8-bit values. | ||
data[i] = x != 0 | ||
} | ||
case []int8: | ||
for i, x := range bs { | ||
data[i] = int8(x) | ||
} | ||
case []uint8: | ||
copy(data, bs) | ||
case []int16: | ||
for i := range data { | ||
data[i] = int16(order.Uint16(bs[2*i:])) | ||
} | ||
case []uint16: | ||
for i := range data { | ||
data[i] = order.Uint16(bs[2*i:]) | ||
} | ||
case []int32: | ||
for i := range data { | ||
data[i] = int32(order.Uint32(bs[4*i:])) | ||
} | ||
case []uint32: | ||
for i := range data { | ||
data[i] = order.Uint32(bs[4*i:]) | ||
} | ||
case []int64: | ||
for i := range data { | ||
data[i] = int64(order.Uint64(bs[8*i:])) | ||
} | ||
case []uint64: | ||
for i := range data { | ||
data[i] = order.Uint64(bs[8*i:]) | ||
} | ||
default: | ||
n = 0 // fast path doesn't apply | ||
} | ||
if n != 0 { | ||
return nil | ||
} | ||
} | ||
|
||
return errors.New("binary.Read: invalid type " + reflect.TypeOf(data).String()) | ||
} | ||
|
||
// intDataSize returns the size of the data required to represent the data when encoded. | ||
// It returns zero if the type cannot be implemented by the fast path in Read or Write. | ||
func intDataSize(data any) int { | ||
switch data := data.(type) { | ||
case bool, int8, uint8, *bool, *int8, *uint8: | ||
return 1 | ||
case []bool: | ||
return len(data) | ||
case []int8: | ||
return len(data) | ||
case []uint8: | ||
return len(data) | ||
case int16, uint16, *int16, *uint16: | ||
return 2 | ||
case []int16: | ||
return 2 * len(data) | ||
case []uint16: | ||
return 2 * len(data) | ||
case int32, uint32, *int32, *uint32: | ||
return 4 | ||
case []int32: | ||
return 4 * len(data) | ||
case []uint32: | ||
return 4 * len(data) | ||
case int64, uint64, *int64, *uint64: | ||
return 8 | ||
case []int64: | ||
return 8 * len(data) | ||
case []uint64: | ||
return 8 * len(data) | ||
} | ||
return 0 | ||
} |
Oops, something went wrong.