The benchstat tool allows custom grouping when comparing with what it calls "sub-name configuration keys": https://pkg.go.dev/golang.org/x/perf@v0.0.0-20250813145418-2f7363a06fe1/cmd/benchstat#hdr-Configuring_comparisons That's quite useful for these benchmarks, as we basically have two independent configs: The type of benchmark and the size. Real example usage for the prepared named statements PR (results are rubbish for unrelated reasons): ``` $ benchstat -row ".name /n" bench-main.out bench-prepared.out goos: linux goarch: amd64 pkg: github.com/syncthing/syncthing/internal/db/sqlite cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz │ bench-main-20250823_014059.out │ bench-prepared-20250823_022849.out │ │ sec/op │ sec/op vs base │ Update Insert100Loc 248.5m ± 8% ¹ 157.7m ± 7% ¹ -36.54% (p=0.000 n=50) Update RepBlocks100 253.7m ± 4% ¹ 163.6m ± 7% ¹ -35.49% (p=0.000 n=50) Update RepSame100 130.42m ± 3% ¹ 60.26m ± 2% ¹ -53.80% (p=0.000 n=50) Update Insert100Rem 38.54m ± 5% ¹ 21.94m ± 1% ¹ -43.07% (p=0.000 n=50) Update GetGlobal100 10.897m ± 4% ¹ 4.231m ± 1% ¹ -61.17% (p=0.000 n=50) Update LocalSequenced 7.560m ± 5% ¹ 3.124m ± 2% ¹ -58.68% (p=0.000 n=50) Update GetDeviceSequenceLoc 17.554µ ± 6% ¹ 8.400µ ± 1% ¹ -52.15% (n=50) Update GetDeviceSequenceRem 17.727µ ± 4% ¹ 8.237µ ± 2% ¹ -53.54% (p=0.000 n=50) Update RemoteNeed 4.147 ± 77% ¹ 1.903 ± 78% ¹ -54.11% (p=0.000 n=50) Update LocalNeed100Largest 21.516m ± 22% ¹ 9.312m ± 47% ¹ -56.72% (p=0.000 n=50) geomean 15.35m 7.486m -51.22% ¹ benchmarks vary in .fullname ```
244 lines
5.8 KiB
Go
244 lines
5.8 KiB
Go
// Copyright (C) 2025 The Syncthing Authors.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/syncthing/syncthing/internal/timeutil"
|
|
"github.com/syncthing/syncthing/lib/config"
|
|
"github.com/syncthing/syncthing/lib/protocol"
|
|
"github.com/syncthing/syncthing/lib/rand"
|
|
)
|
|
|
|
var globalFi protocol.FileInfo
|
|
|
|
func BenchmarkUpdate(b *testing.B) {
|
|
db, err := OpenTemp()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
b.Cleanup(func() {
|
|
if err := db.Close(); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
})
|
|
svc := db.Service(time.Hour).(*Service)
|
|
|
|
fs := make([]protocol.FileInfo, 100)
|
|
seed := 0
|
|
|
|
size := 10000
|
|
for size < 200_000 {
|
|
t0 := time.Now()
|
|
if err := svc.periodic(context.Background()); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
b.Log("garbage collect in", time.Since(t0))
|
|
|
|
for {
|
|
local, err := db.CountLocal(folderID, protocol.LocalDeviceID)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
if local.Files >= size {
|
|
break
|
|
}
|
|
fs := make([]protocol.FileInfo, 1000)
|
|
for i := range fs {
|
|
fs[i] = genFile(rand.String(24), 64, 0)
|
|
}
|
|
if err := db.Update(folderID, protocol.LocalDeviceID, fs); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
|
|
b.Run(fmt.Sprintf("n=Insert100Loc/size=%d", size), func(b *testing.B) {
|
|
for range b.N {
|
|
for i := range fs {
|
|
fs[i] = genFile(rand.String(24), 64, 0)
|
|
}
|
|
if err := db.Update(folderID, protocol.LocalDeviceID, fs); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
b.ReportMetric(float64(b.N)*100.0/b.Elapsed().Seconds(), "files/s")
|
|
})
|
|
|
|
b.Run(fmt.Sprintf("n=RepBlocks100/size=%d", size), func(b *testing.B) {
|
|
for range b.N {
|
|
for i := range fs {
|
|
fs[i].Blocks = genBlocks(fs[i].Name, seed, 64)
|
|
fs[i].Version = fs[i].Version.Update(42)
|
|
}
|
|
seed++
|
|
if err := db.Update(folderID, protocol.LocalDeviceID, fs); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
b.ReportMetric(float64(b.N)*100.0/b.Elapsed().Seconds(), "files/s")
|
|
})
|
|
|
|
b.Run(fmt.Sprintf("n=RepSame100/size=%d", size), func(b *testing.B) {
|
|
for range b.N {
|
|
for i := range fs {
|
|
fs[i].Version = fs[i].Version.Update(42)
|
|
}
|
|
if err := db.Update(folderID, protocol.LocalDeviceID, fs); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
b.ReportMetric(float64(b.N)*100.0/b.Elapsed().Seconds(), "files/s")
|
|
})
|
|
|
|
b.Run(fmt.Sprintf("n=Insert100Rem/size=%d", size), func(b *testing.B) {
|
|
for range b.N {
|
|
for i := range fs {
|
|
fs[i].Blocks = genBlocks(fs[i].Name, seed, 64)
|
|
fs[i].Version = fs[i].Version.Update(42)
|
|
fs[i].Sequence = timeutil.StrictlyMonotonicNanos()
|
|
}
|
|
if err := db.Update(folderID, protocol.DeviceID{42}, fs); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
b.ReportMetric(float64(b.N)*100.0/b.Elapsed().Seconds(), "files/s")
|
|
})
|
|
|
|
b.Run(fmt.Sprintf("n=GetGlobal100/size=%d", size), func(b *testing.B) {
|
|
for range b.N {
|
|
for i := range fs {
|
|
_, ok, err := db.GetGlobalFile(folderID, fs[i].Name)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
if !ok {
|
|
b.Fatal("should exist")
|
|
}
|
|
}
|
|
}
|
|
b.ReportMetric(float64(b.N)*100.0/b.Elapsed().Seconds(), "files/s")
|
|
})
|
|
|
|
b.Run(fmt.Sprintf("n=LocalSequenced/size=%d", size), func(b *testing.B) {
|
|
count := 0
|
|
for range b.N {
|
|
cur, err := db.GetDeviceSequence(folderID, protocol.LocalDeviceID)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
it, errFn := db.AllLocalFilesBySequence(folderID, protocol.LocalDeviceID, cur-100, 0)
|
|
for f := range it {
|
|
count++
|
|
globalFi = f
|
|
}
|
|
if err := errFn(); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
b.ReportMetric(float64(count)/b.Elapsed().Seconds(), "files/s")
|
|
})
|
|
|
|
b.Run(fmt.Sprintf("n=GetDeviceSequenceLoc/size=%d", size), func(b *testing.B) {
|
|
for range b.N {
|
|
_, err := db.GetDeviceSequence(folderID, protocol.LocalDeviceID)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
})
|
|
b.Run(fmt.Sprintf("n=GetDeviceSequenceRem/size=%d", size), func(b *testing.B) {
|
|
for range b.N {
|
|
_, err := db.GetDeviceSequence(folderID, protocol.DeviceID{42})
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run(fmt.Sprintf("n=RemoteNeed/size=%d", size), func(b *testing.B) {
|
|
count := 0
|
|
for range b.N {
|
|
it, errFn := db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0)
|
|
for f := range it {
|
|
count++
|
|
globalFi = f
|
|
}
|
|
if err := errFn(); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
b.ReportMetric(float64(count)/b.Elapsed().Seconds(), "files/s")
|
|
})
|
|
|
|
b.Run(fmt.Sprintf("n=LocalNeed100Largest/size=%d", size), func(b *testing.B) {
|
|
count := 0
|
|
for range b.N {
|
|
it, errFn := db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderLargestFirst, 100, 0)
|
|
for f := range it {
|
|
globalFi = f
|
|
count++
|
|
}
|
|
if err := errFn(); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
b.ReportMetric(float64(count)/b.Elapsed().Seconds(), "files/s")
|
|
})
|
|
|
|
size <<= 1
|
|
}
|
|
}
|
|
|
|
func TestBenchmarkDropAllRemote(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("slow test")
|
|
}
|
|
|
|
db, err := OpenTemp()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := db.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
|
|
fs := make([]protocol.FileInfo, 1000)
|
|
seq := 0
|
|
for {
|
|
local, err := db.CountLocal(folderID, protocol.LocalDeviceID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if local.Files >= 15_000 {
|
|
break
|
|
}
|
|
for i := range fs {
|
|
seq++
|
|
fs[i] = genFile(rand.String(24), 64, seq)
|
|
}
|
|
if err := db.Update(folderID, protocol.DeviceID{42}, fs); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := db.Update(folderID, protocol.LocalDeviceID, fs); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
t0 := time.Now()
|
|
if err := db.DropAllFiles(folderID, protocol.DeviceID{42}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d := time.Since(t0)
|
|
t.Log("drop all took", d)
|
|
}
|