[#376] netmap: Make attributes a non-pointer slice

The speed of copying (which is done regulary for e.g. subnet changes)
is less, however it isn't on the hot path and the absolute time
difference is insignificant.
```
name              old time/op    new time/op    delta
NodeAttributes-8    96.2ns ± 1%   158.3ns ± 1%  +64.61%  (p=0.000 n=10+10)

name              old alloc/op   new alloc/op   delta
NodeAttributes-8     32.0B ± 0%     32.0B ± 0%     ~     (all equal)

name              old allocs/op  new allocs/op  delta
NodeAttributes-8      2.00 ± 0%      2.00 ± 0%     ~     (all equal)
```

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2022-03-02 10:40:22 +03:00 committed by Alex Vanin
parent 2f0eee96fc
commit 6f4908edc2
6 changed files with 82 additions and 40 deletions

View file

@ -101,12 +101,10 @@ func WriteSubnetInfo(node *NodeInfo, info NodeSubnetInfo) {
} }
if !presented { if !presented {
var attr Attribute index := len(attrs)
attrs = append(attrs, Attribute{})
attr.SetKey(key) attrs[index].SetKey(key)
attr.SetValue(val) attrs[index].SetValue(val)
attrs = append(attrs, &attr)
} }
} }
@ -208,12 +206,10 @@ func IterateSubnets(node *NodeInfo, f func(refs.SubnetID) error) error {
} }
// zero subnet should be clearly removed with False value // zero subnet should be clearly removed with False value
var attr Attribute index := len(attrs)
attrs = append(attrs, Attribute{})
attr.SetKey(subnetAttributeKey(&id)) attrs[index].SetKey(subnetAttributeKey(&id))
attr.SetValue(attrSubnetValExit) attrs[index].SetValue(attrSubnetValExit)
attrs = append(attrs, &attr)
} else { } else {
entries++ entries++
} }

View file

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/nspcc-dev/neofs-api-go/v2/netmap" "github.com/nspcc-dev/neofs-api-go/v2/netmap"
netmaptest "github.com/nspcc-dev/neofs-api-go/v2/netmap/test"
"github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -17,6 +18,57 @@ func assertSubnetAttrKey(t *testing.T, attr *netmap.Attribute, num uint32) {
require.Equal(t, subnetAttrKey(strconv.FormatUint(uint64(num), 10)), attr.GetKey()) require.Equal(t, subnetAttrKey(strconv.FormatUint(uint64(num), 10)), attr.GetKey())
} }
func BenchmarkNodeAttributes(b *testing.B) {
const size = 50
id := new(refs.SubnetID)
id.SetValue(12)
attrs := make([]netmap.Attribute, size)
for i := range attrs {
if i == size/2 {
attrs[i] = *netmaptest.GenerateAttribute(false)
} else {
data, err := id.MarshalText()
require.NoError(b, err)
attrs[i].SetKey(subnetAttrKey(string(data)))
attrs[i].SetValue("True")
}
}
var info netmap.NodeSubnetInfo
info.SetID(id)
info.SetEntryFlag(false)
node := new(netmap.NodeInfo)
// When using a single slice `StartTimer` overhead is comparable to the
// function execution time, so we reduce this cost by updating slices in groups.
const cacheSize = 1000
a := make([][]netmap.Attribute, cacheSize)
for i := range a {
a[i] = make([]netmap.Attribute, size)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if i%cacheSize == 0 {
b.StopTimer()
for j := range a {
copy(a[j], attrs)
}
b.StartTimer()
}
node.SetAttributes(a[i%cacheSize])
netmap.WriteSubnetInfo(node, info)
if len(node.GetAttributes())+1 != len(attrs) {
b.FailNow()
}
}
}
func TestWriteSubnetInfo(t *testing.T) { func TestWriteSubnetInfo(t *testing.T) {
t.Run("entry", func(t *testing.T) { t.Run("entry", func(t *testing.T) {
t.Run("zero subnet", func(t *testing.T) { t.Run("zero subnet", func(t *testing.T) {
@ -40,7 +92,7 @@ func TestWriteSubnetInfo(t *testing.T) {
attrs = node.GetAttributes() attrs = node.GetAttributes()
require.Len(t, attrs, 1) require.Len(t, attrs, 1)
attr := attrs[0] attr := &attrs[0]
assertSubnetAttrKey(t, attr, 0) assertSubnetAttrKey(t, attr, 0)
require.Equal(t, "False", attr.GetValue()) require.Equal(t, "False", attr.GetValue())
@ -76,7 +128,7 @@ func TestWriteSubnetInfo(t *testing.T) {
attrs := node.GetAttributes() attrs := node.GetAttributes()
require.Len(t, attrs, 1) require.Len(t, attrs, 1)
attr := attrs[0] attr := &attrs[0]
assertSubnetAttrKey(t, attr, num) assertSubnetAttrKey(t, attr, num)
require.Equal(t, "True", attr.GetValue()) require.Equal(t, "True", attr.GetValue())
@ -128,7 +180,7 @@ func TestSubnets(t *testing.T) {
attrExit.SetKey(subnetAttrKey(strconv.FormatUint(numExit, 10))) attrExit.SetKey(subnetAttrKey(strconv.FormatUint(numExit, 10)))
attrExit.SetValue("False") attrExit.SetValue("False")
attrs := []*netmap.Attribute{&attrEntry, &attrEntry} attrs := []netmap.Attribute{attrEntry, attrEntry}
node.SetAttributes(attrs) node.SetAttributes(attrs)
@ -157,7 +209,7 @@ func TestSubnets(t *testing.T) {
assertErr := func(attr netmap.Attribute) { assertErr := func(attr netmap.Attribute) {
var node netmap.NodeInfo var node netmap.NodeInfo
node.SetAttributes([]*netmap.Attribute{&attr}) node.SetAttributes([]netmap.Attribute{attr})
require.Error(t, netmap.IterateSubnets(&node, func(refs.SubnetID) error { require.Error(t, netmap.IterateSubnets(&node, func(refs.SubnetID) error {
return nil return nil
@ -200,7 +252,7 @@ func TestSubnets(t *testing.T) {
attr.SetKey(subnetAttrKey("321")) attr.SetKey(subnetAttrKey("321"))
attr.SetValue("True") attr.SetValue("True")
attrs := []*netmap.Attribute{&attr} attrs := []netmap.Attribute{attr}
node.SetAttributes(attrs) node.SetAttributes(attrs)
err := netmap.IterateSubnets(&node, func(id refs.SubnetID) error { err := netmap.IterateSubnets(&node, func(id refs.SubnetID) error {
@ -237,7 +289,7 @@ func TestSubnets(t *testing.T) {
attr.SetKey(subnetAttrKey("99")) attr.SetKey(subnetAttrKey("99"))
attr.SetValue("True") attr.SetValue("True")
attrs := []*netmap.Attribute{&attr} attrs := []netmap.Attribute{attr}
node.SetAttributes(attrs) node.SetAttributes(attrs)
err := netmap.IterateSubnets(&node, func(id refs.SubnetID) error { err := netmap.IterateSubnets(&node, func(id refs.SubnetID) error {
@ -257,7 +309,7 @@ func TestSubnets(t *testing.T) {
t.Run("all", func(t *testing.T) { t.Run("all", func(t *testing.T) {
var ( var (
node netmap.NodeInfo node netmap.NodeInfo
attrs []*netmap.Attribute attrs []netmap.Attribute
) )
// enter to some non-zero subnet so that zero is not the only one // enter to some non-zero subnet so that zero is not the only one
@ -267,7 +319,7 @@ func TestSubnets(t *testing.T) {
attr.SetKey(subnetAttrKey(strconv.Itoa(i))) attr.SetKey(subnetAttrKey(strconv.Itoa(i)))
attr.SetValue("True") attr.SetValue("True")
attrs = append(attrs, &attr) attrs = append(attrs, attr)
} }
node.SetAttributes(attrs) node.SetAttributes(attrs)
@ -293,7 +345,7 @@ func TestSubnets(t *testing.T) {
attrOther.SetKey(subnetAttrKey("1")) attrOther.SetKey(subnetAttrKey("1"))
attrOther.SetValue("True") attrOther.SetValue("True")
node.SetAttributes([]*netmap.Attribute{&attrZero, &attrOther}) node.SetAttributes([]netmap.Attribute{attrZero, attrOther})
calledCount := 0 calledCount := 0

View file

@ -297,7 +297,7 @@ func (a *Attribute) FromGRPCMessage(m grpc.Message) error {
return nil return nil
} }
func AttributesToGRPC(as []*Attribute) (res []*netmap.NodeInfo_Attribute) { func AttributesToGRPC(as []Attribute) (res []*netmap.NodeInfo_Attribute) {
if as != nil { if as != nil {
res = make([]*netmap.NodeInfo_Attribute, 0, len(as)) res = make([]*netmap.NodeInfo_Attribute, 0, len(as))
@ -309,23 +309,17 @@ func AttributesToGRPC(as []*Attribute) (res []*netmap.NodeInfo_Attribute) {
return return
} }
func AttributesFromGRPC(as []*netmap.NodeInfo_Attribute) (res []*Attribute, err error) { func AttributesFromGRPC(as []*netmap.NodeInfo_Attribute) (res []Attribute, err error) {
if as != nil { if as != nil {
res = make([]*Attribute, 0, len(as)) res = make([]Attribute, len(as))
for i := range as { for i := range as {
var a *Attribute
if as[i] != nil { if as[i] != nil {
a = new(Attribute) err = res[i].FromGRPCMessage(as[i])
err = a.FromGRPCMessage(as[i])
if err != nil { if err != nil {
return return
} }
} }
res = append(res, a)
} }
} }

View file

@ -383,7 +383,7 @@ func (ni *NodeInfo) StableMarshal(buf []byte) ([]byte, error) {
offset += n offset += n
for i := range ni.attributes { for i := range ni.attributes {
n, err = protoutil.NestedStructureMarshal(attributesNodeInfoField, buf[offset:], ni.attributes[i]) n, err = protoutil.NestedStructureMarshal(attributesNodeInfoField, buf[offset:], &ni.attributes[i])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -408,7 +408,7 @@ func (ni *NodeInfo) StableSize() (size int) {
size += protoutil.RepeatedStringSize(addressNodeInfoField, ni.addresses) size += protoutil.RepeatedStringSize(addressNodeInfoField, ni.addresses)
for i := range ni.attributes { for i := range ni.attributes {
size += protoutil.NestedStructureSize(attributesNodeInfoField, ni.attributes[i]) size += protoutil.NestedStructureSize(attributesNodeInfoField, &ni.attributes[i])
} }
size += protoutil.EnumSize(stateNodeInfoField, int32(ni.state)) size += protoutil.EnumSize(stateNodeInfoField, int32(ni.state))

View file

@ -119,13 +119,13 @@ func GenerateAttribute(empty bool) *netmap.Attribute {
return m return m
} }
func GenerateAttributes(empty bool) []*netmap.Attribute { func GenerateAttributes(empty bool) []netmap.Attribute {
var res []*netmap.Attribute var res []netmap.Attribute
if !empty { if !empty {
res = append(res, res = append(res,
GenerateAttribute(false), *GenerateAttribute(false),
GenerateAttribute(false), *GenerateAttribute(false),
) )
} }

View file

@ -73,7 +73,7 @@ type Attribute struct {
type NodeInfo struct { type NodeInfo struct {
publicKey []byte publicKey []byte
addresses []string addresses []string
attributes []*Attribute attributes []Attribute
state NodeState state NodeState
} }
@ -444,7 +444,7 @@ func (ni *NodeInfo) IterateAddresses(f func(string) bool) {
} }
} }
func (ni *NodeInfo) GetAttributes() []*Attribute { func (ni *NodeInfo) GetAttributes() []Attribute {
if ni != nil { if ni != nil {
return ni.attributes return ni.attributes
} }
@ -452,7 +452,7 @@ func (ni *NodeInfo) GetAttributes() []*Attribute {
return nil return nil
} }
func (ni *NodeInfo) SetAttributes(v []*Attribute) { func (ni *NodeInfo) SetAttributes(v []Attribute) {
if ni != nil { if ni != nil {
ni.attributes = v ni.attributes = v
} }