io: provide generic-based WriteArray

goos: linux
goarch: amd64
pkg: github.com/nspcc-dev/neo-go/pkg/io
cpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics
BenchmarkWriteArray/WriteArray_method,_value-16                  2419993               506.1 ns/op           184 B/op         11 allocs/op
BenchmarkWriteArray/WriteArray_method,_pointer-16                4159906               294.7 ns/op            24 B/op          1 allocs/op
BenchmarkWriteArray/WriteArray_generic,_value-16                 9217713               128.8 ns/op             0 B/op          0 allocs/op
BenchmarkWriteArray/WriteArray_generic,_pointer-16               9575917               131.8 ns/op             0 B/op          0 allocs/op
BenchmarkWriteArray/open-coded,_value-16                         9100132               131.4 ns/op             0 B/op          0 allocs/op
BenchmarkWriteArray/open-coded,_pointer-16                       9153250               131.9 ns/op             0 B/op          0 allocs/op
PASS
ok      github.com/nspcc-dev/neo-go/pkg/io      541.663s

Signed-off-by: Roman Khimov <roman@nspcc.ru>
This commit is contained in:
Roman Khimov 2024-08-29 18:47:02 +03:00
parent dc6c195637
commit c953c6cece
2 changed files with 110 additions and 0 deletions

98
pkg/io/bench_test.go Normal file
View file

@ -0,0 +1,98 @@
package io
import (
"slices"
"testing"
)
type someval struct {
a int
b int
}
func (s someval) EncodeBinary(w *BinWriter) {
w.WriteU64LE(uint64(s.a))
w.WriteU64LE(uint64(s.b))
}
type somepoint struct {
a int
b int
}
func (s *somepoint) EncodeBinary(w *BinWriter) {
w.WriteU64LE(uint64(s.a))
w.WriteU64LE(uint64(s.b))
}
func BenchmarkWriteArray(b *testing.B) {
const numElems = 10
var (
v = slices.Repeat([]someval{{}}, numElems)
p = slices.Repeat([]*somepoint{{}}, numElems)
)
w := NewBufBinWriter()
w.Grow(numElems * 32) // more than needed, we don't need reallocations here.
b.Run("WriteArray method, value", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
w.Reset()
b.StartTimer()
w.WriteArray(v)
}
})
b.Run("WriteArray method, pointer", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
w.Reset()
b.StartTimer()
w.WriteArray(p)
}
})
b.Run("WriteArray generic, value", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
w.Reset()
b.StartTimer()
WriteArray(w.BinWriter, v)
}
})
b.Run("WriteArray generic, pointer", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
w.Reset()
b.StartTimer()
WriteArray(w.BinWriter, p)
}
})
b.Run("open-coded, value", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
w.Reset()
b.StartTimer()
w.WriteVarUint(uint64(len(v)))
for i := range v {
v[i].EncodeBinary(w.BinWriter)
}
}
})
b.Run("open-coded, pointer", func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
w.Reset()
b.StartTimer()
w.WriteVarUint(uint64(len(p)))
for i := range v {
p[i].EncodeBinary(w.BinWriter)
}
}
})
}

View file

@ -68,6 +68,9 @@ func (w *BinWriter) WriteBool(b bool) {
// WriteArray writes a slice or an array arr into w. Note that nil slices and
// empty slices are gonna be treated the same resulting in an equal zero-length
// array encoded.
//
// Deprecated: Go doesn't support generic methods, but [WriteArray] function
// is much faster that this method.
func (w *BinWriter) WriteArray(arr any) {
switch val := reflect.ValueOf(arr); val.Kind() {
case reflect.Slice, reflect.Array:
@ -94,6 +97,15 @@ func (w *BinWriter) WriteArray(arr any) {
}
}
// WriteArray writes a slice arr into w. It is a generic-based version of
// [BinWriter.WriteArray] which works much faster.
func WriteArray[Slice ~[]E, E Serializable](w *BinWriter, arr Slice) {
w.WriteVarUint(uint64(len(arr)))
for i := range arr {
arr[i].EncodeBinary(w)
}
}
// WriteVarUint writes a uint64 into the underlying writer using variable-length encoding.
func (w *BinWriter) WriteVarUint(val uint64) {
if w.Err != nil {