mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-08 15:41:15 +02:00
perf(weed/storage/super_block): intern the byte-encoded replica placements (#10610)
NewReplicaPlacementFromByte formatted the byte with fmt.Sprintf and parsed the result back, allocating a string and a ReplicaPlacement every call. The master calls it once per volume in every heartbeat, and keeps the pointer for the lifetime of the volume, so a cluster with 1.6M volume replicas carries 1.6M of these where a handful of distinct values exist. The table is a flat pointer-free array, so it costs 6KB of static data and no heap objects however few placements a cluster actually uses. A byte only ever decodes to a valid placement, so the table is complete and the error return stays nil. BenchmarkSyncDataNodeRegistration/100000Volumes 500601 allocs/op -> 300589 allocs/op
This commit is contained in:
@@ -43,8 +43,26 @@ func NewReplicaPlacementFromString(t string) (*ReplicaPlacement, error) {
|
|||||||
return rp, nil
|
return rp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// replicaPlacementsByByte is one 6KB pointer-free array covering every encoding
|
||||||
|
// a byte can hold. The master decodes one per volume in every volume server
|
||||||
|
// heartbeat and keeps it for the volume's lifetime, so handing out a shared
|
||||||
|
// element keeps that path off fmt and off the heap entirely.
|
||||||
|
//
|
||||||
|
// Elements are immutable. Callers must not write through the returned pointer.
|
||||||
|
var replicaPlacementsByByte [256]ReplicaPlacement
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for b := range replicaPlacementsByByte {
|
||||||
|
replicaPlacementsByByte[b] = ReplicaPlacement{
|
||||||
|
DiffDataCenterCount: b / 100,
|
||||||
|
DiffRackCount: b / 10 % 10,
|
||||||
|
SameRackCount: b % 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewReplicaPlacementFromByte(b byte) (*ReplicaPlacement, error) {
|
func NewReplicaPlacementFromByte(b byte) (*ReplicaPlacement, error) {
|
||||||
return NewReplicaPlacementFromString(fmt.Sprintf("%03d", b))
|
return &replicaPlacementsByByte[b], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *ReplicaPlacement) HasReplication() bool {
|
func (rp *ReplicaPlacement) HasReplication() bool {
|
||||||
|
|||||||
@@ -1,9 +1,29 @@
|
|||||||
package super_block
|
package super_block
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestReplicaPlacementFromByteMatchesString(t *testing.T) {
|
||||||
|
for b := 0; b < 256; b++ {
|
||||||
|
want, err := NewReplicaPlacementFromString(fmt.Sprintf("%03d", b))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("byte %d: %v", b, err)
|
||||||
|
}
|
||||||
|
got, err := NewReplicaPlacementFromByte(byte(b))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("byte %d: %v", b, err)
|
||||||
|
}
|
||||||
|
if !got.Equals(want) {
|
||||||
|
t.Errorf("byte %d: got %+v, want %+v", b, got, want)
|
||||||
|
}
|
||||||
|
if got.Byte() != byte(b) {
|
||||||
|
t.Errorf("byte %d: round trip gave %d", b, got.Byte())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReplicaPlacementSerialDeserial(t *testing.T) {
|
func TestReplicaPlacementSerialDeserial(t *testing.T) {
|
||||||
rp, _ := NewReplicaPlacementFromString("001")
|
rp, _ := NewReplicaPlacementFromString("001")
|
||||||
newRp, _ := NewReplicaPlacementFromByte(rp.Byte())
|
newRp, _ := NewReplicaPlacementFromByte(rp.Byte())
|
||||||
|
|||||||
Reference in New Issue
Block a user