internal/restic: Don't allocate in Tree.Insert

name         old time/op    new time/op    delta
BuildTree-8    34.6µs ± 4%     7.0µs ± 3%  -79.68%  (p=0.000 n=18+19)

name         old alloc/op   new alloc/op   delta
BuildTree-8    34.0kB ± 0%     0.9kB ± 0%  -97.37%  (p=0.000 n=20+20)

name         old allocs/op  new allocs/op  delta
BuildTree-8       108 ± 0%         1 ± 0%  -99.07%  (p=0.000 n=20+20)
This commit is contained in:
greatroar 2021-09-26 17:18:42 +02:00
parent 78dac2fd48
commit c892c0bab9
6 changed files with 35 additions and 11 deletions

View File

@ -107,7 +107,7 @@ func runRecover(gopts GlobalOptions) error {
return nil return nil
} }
tree := restic.NewTree() tree := restic.NewTree(len(roots))
for id := range roots { for id := range roots {
var subtreeID = id var subtreeID = id
node := restic.Node{ node := restic.Node{

View File

@ -550,12 +550,13 @@ func (arch *Archiver) statDir(dir string) (os.FileInfo, error) {
func (arch *Archiver) SaveTree(ctx context.Context, snPath string, atree *Tree, previous *restic.Tree) (*restic.Tree, error) { func (arch *Archiver) SaveTree(ctx context.Context, snPath string, atree *Tree, previous *restic.Tree) (*restic.Tree, error) {
debug.Log("%v (%v nodes), parent %v", snPath, len(atree.Nodes), previous) debug.Log("%v (%v nodes), parent %v", snPath, len(atree.Nodes), previous)
tree := restic.NewTree() nodeNames := atree.NodeNames()
tree := restic.NewTree(len(nodeNames))
futureNodes := make(map[string]FutureNode) futureNodes := make(map[string]FutureNode)
// iterate over the nodes of atree in lexicographic (=deterministic) order // iterate over the nodes of atree in lexicographic (=deterministic) order
for _, name := range atree.NodeNames() { for _, name := range nodeNames {
subatree := atree.Nodes[name] subatree := atree.Nodes[name]
// test if context has been cancelled // test if context has been cancelled

View File

@ -103,7 +103,8 @@ type saveTreeResponse struct {
func (s *TreeSaver) save(ctx context.Context, snPath string, node *restic.Node, nodes []FutureNode) (*restic.Node, ItemStats, error) { func (s *TreeSaver) save(ctx context.Context, snPath string, node *restic.Node, nodes []FutureNode) (*restic.Node, ItemStats, error) {
var stats ItemStats var stats ItemStats
tree := restic.NewTree() tree := restic.NewTree(len(nodes))
for _, fn := range nodes { for _, fn := range nodes {
fn.wait(ctx) fn.wait(ctx)

View File

@ -14,10 +14,10 @@ type Tree struct {
Nodes []*Node `json:"nodes"` Nodes []*Node `json:"nodes"`
} }
// NewTree creates a new tree object. // NewTree creates a new tree object with the given initial capacity.
func NewTree() *Tree { func NewTree(capacity int) *Tree {
return &Tree{ return &Tree{
Nodes: []*Node{}, Nodes: make([]*Node, 0, capacity),
} }
} }
@ -51,8 +51,8 @@ func (t *Tree) Insert(node *Node) error {
return errors.Errorf("node %q already present", node.Name) return errors.Errorf("node %q already present", node.Name)
} }
// https://code.google.com/p/go-wiki/wiki/SliceTricks // https://github.com/golang/go/wiki/SliceTricks
t.Nodes = append(t.Nodes, &Node{}) t.Nodes = append(t.Nodes, nil)
copy(t.Nodes[pos+1:], t.Nodes[pos:]) copy(t.Nodes[pos+1:], t.Nodes[pos:])
t.Nodes[pos] = node t.Nodes[pos] = node

View File

@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"testing" "testing"
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
@ -98,7 +99,7 @@ func TestLoadTree(t *testing.T) {
defer cleanup() defer cleanup()
// save tree // save tree
tree := restic.NewTree() tree := restic.NewTree(0)
id, err := repo.SaveTree(context.TODO(), tree) id, err := repo.SaveTree(context.TODO(), tree)
rtest.OK(t, err) rtest.OK(t, err)
@ -113,3 +114,24 @@ func TestLoadTree(t *testing.T) {
"trees are not equal: want %v, got %v", "trees are not equal: want %v, got %v",
tree, tree2) tree, tree2)
} }
func BenchmarkBuildTree(b *testing.B) {
const size = 100 // Directories of this size are not uncommon.
nodes := make([]restic.Node, size)
for i := range nodes {
// Archiver.SaveTree inputs in sorted order, so do that here too.
nodes[i].Name = strconv.Itoa(i)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
t := restic.NewTree(size)
for i := range nodes {
_ = t.Insert(&nodes[i])
}
}
}

View File

@ -23,7 +23,7 @@ func BuildTreeMap(tree TestTree) (m TreeMap, root restic.ID) {
} }
func buildTreeMap(tree TestTree, m TreeMap) restic.ID { func buildTreeMap(tree TestTree, m TreeMap) restic.ID {
res := restic.NewTree() res := restic.NewTree(0)
for name, item := range tree { for name, item := range tree {
switch elem := item.(type) { switch elem := item.(type) {