Benchmarking RamSQL
Dans un article précédent, je faisais le point sur RamSQL et j’avais conclu sur la cohérence de faire une v1
stable au lieu de purement et simplement abandonner le projet.
Dans la roadmap que j’avais esquissé, le premier point était de gérer correctement les transactions. Cette opération nécessitait de réécrire quasiment entièrement le projet pour baser le reste du code dans une transaction.
Quitte à tout casser, j’en ai profité pour ajouter des benchmarks:
bench:
go test -bench=. -count 6 | tee newbench.txt
benchstat bench.txt newbench.txt | tee benchstat.txt
avec comme baseline SQLite
en mode :memory:
func BenchmarkRamSQLSelectHashMap10K(b *testing.B) {
db, err := sql.Open("ramsql", "BenchmarkSQLSelectHashMap10K")
if err != nil {
b.Fatalf("cannot open ramsql db")
}
n := 10000
setupInsertN(b, db, n)
benchmarkSelectHashMap(b, db)
}
func BenchmarkSQLiteSelectHashMap10K(b *testing.B) {
db, err := sql.Open("sqlite", ":memory:")
if err != nil {
b.Fatalf("cannot open sqlite")
}
n := 10000
setupInsertN(b, db, n)
benchmarkSelectHashMap(b, db)
}
func benchmarkSelectHashMap(b *testing.B, db *sql.DB) {
var id int64
var email string
b.ResetTimer()
for n := 0; n < b.N; n++ {
query := `SELECT account.id, account.email FROM account WHERE id = $1`
rows, err := db.Query(query, n)
if err != nil {
b.Fatalf("cannot query rows: %s", err)
}
for rows.Next() {
err = rows.Scan(&id, &email)
if err != nil {
b.Fatalf("cannot scan rows: %s", err)
}
}
}
_ = id
_ = email
_, err := db.Exec(`DROP TABLE account`)
if err != nil {
b.Fatalf("sql.Exec: %s", err)
}
}
go test
nous sort un temps moyen pour chaque opération:
$ go test -bench=. -count 6 | tee newbench.txt
goos: linux
goarch: amd64
pkg: github.com/proullon/ramsql
cpu: Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz
BenchmarkRamSQLSelect-8 3766 297732 ns/op
BenchmarkRamSQLSelect-8 4114 286406 ns/op
BenchmarkRamSQLSelect-8 3974 287603 ns/op
BenchmarkRamSQLSelect-8 3874 286818 ns/op
BenchmarkRamSQLSelect-8 4036 286082 ns/op
BenchmarkRamSQLSelect-8 3924 285952 ns/op
BenchmarkSQLiteSelect-8 110434 10706 ns/op
BenchmarkSQLiteSelect-8 112434 10805 ns/op
BenchmarkSQLiteSelect-8 99657 11055 ns/op
BenchmarkSQLiteSelect-8 111702 10689 ns/op
BenchmarkSQLiteSelect-8 111669 10756 ns/op
BenchmarkSQLiteSelect-8 111285 10638 ns/op
BenchmarkRamSQLSelect10K-8 160 7384466 ns/op
BenchmarkRamSQLSelect10K-8 162 7938318 ns/op
BenchmarkRamSQLSelect10K-8 160 7456492 ns/op
BenchmarkRamSQLSelect10K-8 159 7502256 ns/op
BenchmarkRamSQLSelect10K-8 164 7540972 ns/op
BenchmarkRamSQLSelect10K-8 144 7451605 ns/op
BenchmarkSQLiteSelect10K-8 109460 10979 ns/op
BenchmarkSQLiteSelect10K-8 105711 11033 ns/op
BenchmarkSQLiteSelect10K-8 104372 11035 ns/op
BenchmarkSQLiteSelect10K-8 107712 11063 ns/op
BenchmarkSQLiteSelect10K-8 107395 11060 ns/op
BenchmarkSQLiteSelect10K-8 108666 11116 ns/op
BenchmarkRamSQLInsert10-8 4424 268468 ns/op
BenchmarkRamSQLInsert10-8 4477 298751 ns/op
BenchmarkRamSQLInsert10-8 4242 280029 ns/op
BenchmarkRamSQLInsert10-8 3828 283994 ns/op
BenchmarkRamSQLInsert10-8 4328 283040 ns/op
BenchmarkRamSQLInsert10-8 4436 276176 ns/op
BenchmarkSQLiteInsert10-8 3459 332308 ns/op
BenchmarkSQLiteInsert10-8 3721 328695 ns/op
BenchmarkSQLiteInsert10-8 3709 330266 ns/op
BenchmarkSQLiteInsert10-8 3670 343643 ns/op
BenchmarkSQLiteInsert10-8 3680 320361 ns/op
BenchmarkSQLiteInsert10-8 3819 325789 ns/op
BenchmarkRamSQLSetup-8 1430150 832.9 ns/op
BenchmarkRamSQLSetup-8 1436343 835.1 ns/op
BenchmarkRamSQLSetup-8 1439770 833.9 ns/op
BenchmarkRamSQLSetup-8 1331280 806.1 ns/op
BenchmarkRamSQLSetup-8 1485778 807.0 ns/op
BenchmarkRamSQLSetup-8 1486251 806.3 ns/op
BenchmarkSQLiteSetup-8 1477396 810.5 ns/op
BenchmarkSQLiteSetup-8 1464448 809.5 ns/op
BenchmarkSQLiteSetup-8 1380595 814.2 ns/op
BenchmarkSQLiteSetup-8 1482067 811.0 ns/op
BenchmarkSQLiteSetup-8 1481173 813.0 ns/op
BenchmarkSQLiteSetup-8 1480530 811.3 ns/op
PASS
ok github.com/proullon/ramsql 87.537s
C’est bien mais en l’état c’est inutilisable, donc je passe ensuite benchstat
pour faire un diff entre la version actuelle et la dernière version committed:
$ benchstat bench.txt newbench.txt | tee benchstat.txt
goos: linux
goarch: amd64
pkg: github.com/proullon/ramsql
cpu: Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz
│ bench.txt │ newbench.txt │
│ sec/op │ sec/op vs base │
RamSQLSelect-8 287.8µ ± 4% 286.6µ ± 4% ~ (p=0.818 n=6)
SQLiteSelect-8 10.98µ ± 11% 10.73µ ± 3% ~ (p=0.065 n=6)
RamSQLSelect10K-8 8.246m ± 5% 7.479m ± 6% -9.30% (p=0.004 n=6)
SQLiteSelect10K-8 11.54µ ± 11% 11.05µ ± 1% -4.28% (p=0.002 n=6)
RamSQLInsert10-8 312.3µ ± 4% 281.5µ ± 6% -9.86% (p=0.002 n=6)
SQLiteInsert10-8 341.5µ ± 1% 329.5µ ± 4% -3.53% (p=0.041 n=6)
RamSQLSetup-8 855.3n ± 4% 820.0n ± 2% ~ (p=0.065 n=6)
SQLiteSetup-8 844.1n ± 2% 811.1n ± 0% -3.90% (p=0.002 n=6)
geomean 46.84µ 44.61µ -4.75%
Là c’est très cool, parce que je peux facilement voir la différence de performance avec l’ancienne version (“bench.txt”) et la nouvelle (“vs base”).
Par exemple ci-dessus on constate que la modification amène 10% de gain sur le SELECT
et le INSERT
. On peut aussi voir que le SELECT
de RamSQL est 100x plus lent que celui de SQLite (7479µs vs 11µs)…
Pour aller plus loin et mieux comprendre les endroits bloquant, on peut
$ go test -trace=driver.out ./driver
$ go tool trace --pprof=syscall driver.out
$ go tool pprof -traces syscall.pprof
La version v0.1.3
de RamSQL, après refactoring, est beaucoup plus correcte:
│ bench.txt │ newbench.txt │
│ sec/op │ sec/op vs base │
RamSQLSelectBTree-8 49.68µ ± 4% 54.52µ ± 4% +9.73% (p=0.002 n=6)
SQLiteSelectBTree-8 10.92µ ± 5% 11.43µ ± 3% +4.63% (p=0.026 n=6)
RamSQLSelectBTree10K-8 543.6µ ± 2% 682.3µ ± 17% +25.51% (p=0.002 n=6)
SQLiteSelectBTree10K-8 11.20µ ± 2% 12.50µ ± 12% +11.63% (p=0.002 n=6)
RamSQLSelectHashMap10K-8 24.92µ ± 2% 30.73µ ± 7% +23.32% (p=0.002 n=6)
SQLiteSelectHashMap10K-8 8.450µ ± 2% 8.651µ ± 4% ~ (p=0.132 n=6)
RamSQLSelectBTree100K-8 14.00m ± 2% 16.09m ± 11% +14.93% (p=0.002 n=6)
SQLiteSelectBTree100K-8 11.33µ ± 2% 12.70µ ± 7% +12.04% (p=0.002 n=6)
RamSQLSelectHashMap100K-8 26.47µ ± 2% 29.76µ ± 4% +12.42% (p=0.002 n=6)
SQLiteSelectHashMap100K-8 8.581µ ± 4% 9.469µ ± 8% +10.35% (p=0.002 n=6)
RamSQLInsert10-8 177.9µ ± 2% 211.9µ ± 5% +19.13% (p=0.002 n=6)
SQLiteInsert10-8 330.6µ ± 4% 106.7µ ± 4% -67.73% (p=0.002 n=6)
RamSQLSetup-8 834.9n ± 1% 841.6n ± 2% +0.81% (p=0.004 n=6)
SQLiteSetup-8 826.9n ± 0% 855.4n ± 2% +3.45% (p=0.002 n=6)
geomean 31.66µ 32.26µ +1.89%
- le
INSERT
,Setup
est dans le même ordre de grandeur - le
SELECT
aussi lorsque RamSQL peut utiliser un index HashMap - les index B-Tree sont TODO