mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
raft: add (*RawNode).WithProgress
Calls to Status can be frequent and currently incur three heap allocations, but often the caller has no intention to hold on to the returned status. Add StatusWithoutProgress and WithProgress to allow avoiding heap allocations altogether. StatusWithoutProgress does what's on the tin and additionally returns a value (instead of a pointer) to avoid the associated heap allocation. By not returning a Progress map, it avoids all other allocations that Status incurs. To still introspect the Progress map, add WithProgress, which uses a simple visitor pattern. Add benchmarks to verify that this is indeed allocation free. ``` BenchmarkStatusProgress/members=1/Status-8 5000000 353 ns/op 784 B/op 3 allocs/op BenchmarkStatusProgress/members=1/Status-example-8 5000000 372 ns/op 784 B/op 3 allocs/op BenchmarkStatusProgress/members=1/StatusWithoutProgress-8 100000000 17.6 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=1/WithProgress-8 30000000 48.6 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=1/WithProgress-example-8 30000000 42.9 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=3/Status-8 5000000 395 ns/op 784 B/op 3 allocs/op BenchmarkStatusProgress/members=3/Status-example-8 3000000 449 ns/op 784 B/op 3 allocs/op BenchmarkStatusProgress/members=3/StatusWithoutProgress-8 100000000 18.7 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=3/WithProgress-8 20000000 78.1 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=3/WithProgress-example-8 20000000 70.7 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=5/Status-8 3000000 470 ns/op 784 B/op 3 allocs/op BenchmarkStatusProgress/members=5/Status-example-8 3000000 544 ns/op 784 B/op 3 allocs/op BenchmarkStatusProgress/members=5/StatusWithoutProgress-8 100000000 19.7 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=5/WithProgress-8 20000000 105 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=5/WithProgress-example-8 20000000 94.0 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=100/Status-8 100000 11903 ns/op 22663 B/op 12 allocs/op BenchmarkStatusProgress/members=100/Status-example-8 100000 13330 ns/op 22669 B/op 12 allocs/op BenchmarkStatusProgress/members=100/StatusWithoutProgress-8 50000000 20.9 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=100/WithProgress-8 1000000 1731 ns/op 0 B/op 0 allocs/op BenchmarkStatusProgress/members=100/WithProgress-example-8 1000000 1571 ns/op 0 B/op 0 allocs/op ```
This commit is contained in:
parent
510ae3d2a2
commit
bd332b318e
@ -236,6 +236,39 @@ func (rn *RawNode) Status() *Status {
|
||||
return &status
|
||||
}
|
||||
|
||||
// StatusWithoutProgress returns a Status without populating the Progress field
|
||||
// (and returns the Status as a value to avoid forcing it onto the heap). This
|
||||
// is more performant if the Progress is not required. See WithProgress for an
|
||||
// allocation-free way to introspect the Progress.
|
||||
func (rn *RawNode) StatusWithoutProgress() Status {
|
||||
return getStatusWithoutProgress(rn.raft)
|
||||
}
|
||||
|
||||
// ProgressType indicates the type of replica a Progress corresponds to.
|
||||
type ProgressType byte
|
||||
|
||||
const (
|
||||
// ProgressTypePeer accompanies a Progress for a regular peer replica.
|
||||
ProgressTypePeer ProgressType = iota
|
||||
// ProgressTypeLearner accompanies a Progress for a learner replica.
|
||||
ProgressTypeLearner
|
||||
)
|
||||
|
||||
// WithProgress is a helper to introspect the Progress for this node and its
|
||||
// peers.
|
||||
func (rn *RawNode) WithProgress(visitor func(id uint64, typ ProgressType, pr Progress)) {
|
||||
for id, pr := range rn.raft.prs {
|
||||
pr := *pr
|
||||
pr.ins = nil
|
||||
visitor(id, ProgressTypePeer, pr)
|
||||
}
|
||||
for id, pr := range rn.raft.learnerPrs {
|
||||
pr := *pr
|
||||
pr.ins = nil
|
||||
visitor(id, ProgressTypeLearner, pr)
|
||||
}
|
||||
}
|
||||
|
||||
// ReportUnreachable reports the given node is not reachable for the last send.
|
||||
func (rn *RawNode) ReportUnreachable(id uint64) {
|
||||
_ = rn.raft.Step(pb.Message{Type: pb.MsgUnreachable, From: id})
|
||||
|
@ -16,6 +16,7 @@ package raft
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@ -545,3 +546,73 @@ func TestRawNodeBoundedLogGrowthWithPartition(t *testing.T) {
|
||||
rawNode.Advance(rd)
|
||||
checkUncommitted(0)
|
||||
}
|
||||
|
||||
func BenchmarkStatusProgress(b *testing.B) {
|
||||
setup := func(members int) *RawNode {
|
||||
peers := make([]uint64, members)
|
||||
for i := range peers {
|
||||
peers[i] = uint64(i + 1)
|
||||
}
|
||||
cfg := newTestConfig(1, peers, 3, 1, NewMemoryStorage())
|
||||
cfg.Logger = discardLogger
|
||||
r := newRaft(cfg)
|
||||
r.becomeFollower(1, 1)
|
||||
r.becomeCandidate()
|
||||
r.becomeLeader()
|
||||
return &RawNode{raft: r}
|
||||
}
|
||||
|
||||
for _, members := range []int{1, 3, 5, 100} {
|
||||
b.Run(fmt.Sprintf("members=%d", members), func(b *testing.B) {
|
||||
// NB: call getStatus through rn.Status because that incurs an additional
|
||||
// allocation.
|
||||
rn := setup(members)
|
||||
|
||||
b.Run("Status", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = rn.Status()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Status-example", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s := rn.Status()
|
||||
var n uint64
|
||||
for _, pr := range s.Progress {
|
||||
n += pr.Match
|
||||
}
|
||||
_ = n
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("StatusWithoutProgress", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = rn.StatusWithoutProgress()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("WithProgress", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
visit := func(uint64, ProgressType, Progress) {}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
rn.WithProgress(visit)
|
||||
}
|
||||
})
|
||||
b.Run("WithProgress-example", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var n uint64
|
||||
visit := func(_ uint64, _ ProgressType, pr Progress) {
|
||||
n += pr.Match
|
||||
}
|
||||
rn.WithProgress(visit)
|
||||
_ = n
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -32,29 +32,35 @@ type Status struct {
|
||||
LeadTransferee uint64
|
||||
}
|
||||
|
||||
// getStatus gets a copy of the current raft status.
|
||||
func getStatus(r *raft) Status {
|
||||
func getProgressCopy(r *raft) map[uint64]Progress {
|
||||
prs := make(map[uint64]Progress)
|
||||
for id, p := range r.prs {
|
||||
prs[id] = *p
|
||||
}
|
||||
|
||||
for id, p := range r.learnerPrs {
|
||||
prs[id] = *p
|
||||
}
|
||||
return prs
|
||||
}
|
||||
|
||||
func getStatusWithoutProgress(r *raft) Status {
|
||||
s := Status{
|
||||
ID: r.id,
|
||||
LeadTransferee: r.leadTransferee,
|
||||
}
|
||||
|
||||
s.HardState = r.hardState()
|
||||
s.SoftState = *r.softState()
|
||||
|
||||
s.Applied = r.raftLog.applied
|
||||
return s
|
||||
}
|
||||
|
||||
// getStatus gets a copy of the current raft status.
|
||||
func getStatus(r *raft) Status {
|
||||
s := getStatusWithoutProgress(r)
|
||||
if s.RaftState == StateLeader {
|
||||
s.Progress = make(map[uint64]Progress)
|
||||
for id, p := range r.prs {
|
||||
s.Progress[id] = *p
|
||||
}
|
||||
|
||||
for id, p := range r.learnerPrs {
|
||||
s.Progress[id] = *p
|
||||
}
|
||||
s.Progress = getProgressCopy(r)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user