diff --git a/benchmark/README.md b/benchmark/README.md index 4ef2ca0..4bf80a1 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -12,7 +12,7 @@ Additional command line arguments: | flag | type | default | description | | ---- | ---- | ------- | ----------------------------------------------------------------------------------------------- | -| -mem | bool | false | if set, use in-memory SQLite | +| -mem | bool | false | if set - benchmarks will use in-memory SQLite instance, otherwise: on-disk instance | | -rep | uint | 1 | run each benchmark multiple times and average the results. this may provide more stable results | diff --git a/benchmark/bench.go b/benchmark/bench.go index cf35452..91acc11 100644 --- a/benchmark/bench.go +++ b/benchmark/bench.go @@ -1,6 +1,11 @@ +// Copyright 2021 The Sqlite Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package benchmark -/* this file contains benchmarks inspired by https://www.sqlite.org/speed.html */ +/* +this file contains benchmarks inspired by https://www.sqlite.org/speed.html +*/ import ( "database/sql" @@ -10,7 +15,7 @@ import ( ) // corresponds to Test 1 from https://www.sqlite.org/speed.html -func bench_insert(b *testing.B, db *sql.DB) { +func benchInsert(b *testing.B, db *sql.DB) { // create test table createTestTable(db) @@ -21,7 +26,7 @@ func bench_insert(b *testing.B, db *sql.DB) { } // corresponds to Test 2 from https://www.sqlite.org/speed.html -func bench_insert_in_transaction(b *testing.B, db *sql.DB) { +func benchInsertInTransaction(b *testing.B, db *sql.DB) { // create test table createTestTable(db) @@ -32,7 +37,7 @@ func bench_insert_in_transaction(b *testing.B, db *sql.DB) { } // corresponds to Test 3 from https://www.sqlite.org/speed.html -func bench_insert_into_indexed(b *testing.B, db *sql.DB) { +func benchInsertIntoIndexed(b *testing.B, db *sql.DB) { // create test table with indexed column createTestTable(db, `c`) @@ -42,7 +47,7 @@ func bench_insert_into_indexed(b *testing.B, db *sql.DB) { } // corresponds to Test 4 from https://www.sqlite.org/speed.html -func bench_select_without_index(b *testing.B, db *sql.DB) { +func benchSelectWithoutIndex(b *testing.B, db *sql.DB) { // create test table createTestTable(db) @@ -66,13 +71,15 @@ func bench_select_without_index(b *testing.B, db *sql.DB) { // ... for i := 0; i < b.N; i++ { b := (i * 100) % maxGeneratedNum - stmt.Exec(b, b+1000) + if _, err := stmt.Exec(b, b+1000); err != nil { + panic(err) + } } }) } // corresponds to Test 5 from https://www.sqlite.org/speed.html -func bench_select_on_string_comparison(b *testing.B, db *sql.DB) { +func benchSelectOnStringComparison(b *testing.B, db *sql.DB) { // create test table createTestTable(db) @@ -105,7 +112,7 @@ func bench_select_on_string_comparison(b *testing.B, db *sql.DB) { } // corresponds to Test 6 from https://www.sqlite.org/speed.html -func bench_create_index(b *testing.B, db *sql.DB) { +func benchCreateIndex(b *testing.B, db *sql.DB) { // create test table createTestTable(db) @@ -136,7 +143,7 @@ func bench_create_index(b *testing.B, db *sql.DB) { } // corresponds to Test 7 from https://www.sqlite.org/speed.html -func bench_select_with_index(b *testing.B, db *sql.DB) { +func benchSelectWithIndex(b *testing.B, db *sql.DB) { // create test table with indexed field createTestTable(db, `b`) @@ -159,14 +166,17 @@ func bench_select_with_index(b *testing.B, db *sql.DB) { // SELECT count(*), avg(b) FROM t2 WHERE b>=200 AND b<300; for i := 0; i < b.N; i++ { b := (i * 100) % maxGeneratedNum - stmt.Exec(b, b+100) + if _, err := stmt.Exec(b, b+100); err != nil { + panic(err) + } + } }) } // corresponds to Test 8 from https://www.sqlite.org/speed.html -func bench_update_without_index(b *testing.B, db *sql.DB) { +func benchUpdateWithoutIndex(b *testing.B, db *sql.DB) { // create test table createTestTable(db) @@ -189,14 +199,16 @@ func bench_update_without_index(b *testing.B, db *sql.DB) { // UPDATE t1 SET b=b*2 WHERE a>=20 AND a<30; for i := 0; i < b.N; i++ { a := (i * 10) % testTableRowCount - stmt.Exec(a, a+10) + if _, err := stmt.Exec(a, a+10); err != nil { + panic(err) + } } }) } // corresponds to Test 9 from https://www.sqlite.org/speed.html -func bench_update_with_index(b *testing.B, db *sql.DB) { +func benchUpdateWithIndex(b *testing.B, db *sql.DB) { // create test table createTestTable(db, `a`) @@ -218,16 +230,18 @@ func bench_update_with_index(b *testing.B, db *sql.DB) { // UPDATE t2 SET b=121928 WHERE a=2; // ... for i := 0; i < b.N; i++ { - stmt.Exec( + if _, err := stmt.Exec( rand.Uint32(), // b = ? i%testTableRowCount+1, // WHERE a=? - ) + ); err != nil { + panic(err) + } } }) } // corresponds to Test 10 from https://www.sqlite.org/speed.html -func bench_update_text_with_index(b *testing.B, db *sql.DB) { +func benchUpdateTextWithIndex(b *testing.B, db *sql.DB) { // create test table createTestTable(db, `a`) @@ -249,16 +263,18 @@ func bench_update_text_with_index(b *testing.B, db *sql.DB) { // UPDATE t2 SET c='three hundred sixty six thousand five hundred two' WHERE a=2; for i := 0; i < b.N; i++ { // generate new random number-as-words for c - stmt.Exec( + if _, err := stmt.Exec( pronounceNum(uint32(rand.Int31n(maxGeneratedNum))), // SET c=? i%testTableRowCount+1, // WHERE a=? - ) + ); err != nil { + panic(err) + } } }) } // corresponds to Test 11 from https://www.sqlite.org/speed.html -func bench_insert_from_select(b *testing.B, db *sql.DB) { +func benchInsertFromSelect(b *testing.B, db *sql.DB) { // create source table createTestTable(db) fillTestTableInTx(db, testTableRowCount) @@ -292,7 +308,7 @@ func bench_insert_from_select(b *testing.B, db *sql.DB) { } // corresponds to Test 12 from https://www.sqlite.org/speed.html -func bench_delete_without_index(b *testing.B, db *sql.DB) { +func benchDeleteWithoutIndex(b *testing.B, db *sql.DB) { // create test table createTestTable(db) @@ -319,7 +335,7 @@ func bench_delete_without_index(b *testing.B, db *sql.DB) { } // corresponds to Test 13 from https://www.sqlite.org/speed.html -func bench_delete_with_index(b *testing.B, db *sql.DB) { +func benchDeleteWithIndex(b *testing.B, db *sql.DB) { // create test table with indexed column createTestTable(db, `a`) @@ -346,7 +362,7 @@ func bench_delete_with_index(b *testing.B, db *sql.DB) { } // corresponds to Test 16 from https://www.sqlite.org/speed.html -func bench_drop_table(b *testing.B, db *sql.DB) { +func benchDropTable(b *testing.B, db *sql.DB) { for i := 0; i < b.N; i++ { b.StopTimer() createTestTable(db) diff --git a/benchmark/bench_test.go b/benchmark/bench_test.go index 0e0cc71..211fbe8 100644 --- a/benchmark/bench_test.go +++ b/benchmark/bench_test.go @@ -27,22 +27,22 @@ var ( // benchmark funcs to execute funcs = []func(*testing.B, *sql.DB){ - bench_create_index, - bench_select_on_string_comparison, - bench_select_with_index, - bench_select_without_index, - bench_insert, - bench_insert_in_transaction, - bench_insert_into_indexed, - bench_insert_from_select, - bench_update_text_with_index, - bench_update_with_index, - bench_update_without_index, - bench_delete_without_index, - bench_delete_with_index, + benchCreateIndex, + benchSelectOnStringComparison, + benchSelectWithIndex, + benchSelectWithoutIndex, + benchInsert, + benchInsertInTransaction, + benchInsertIntoIndexed, + benchInsertFromSelect, + benchUpdateTextWithIndex, + benchUpdateWithIndex, + benchUpdateWithoutIndex, + benchDeleteWithoutIndex, + benchDeleteWithIndex, // due to very long run of this benchmark, it is disabled - // bench_drop_table, + // benchDropTable, } ) @@ -53,7 +53,7 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func Test_BenchmarkSQLite(t *testing.T) { +func TestBenchmarkSQLite(t *testing.T) { // print info about CPU and OS fmt.Println() fmt.Printf("goos: %s\n", runtime.GOOS) @@ -69,8 +69,8 @@ func Test_BenchmarkSQLite(t *testing.T) { for _, f := range funcs { var ( - nsPerOpBase avgVal - nsPerOp avgVal + nsPerOpCGo avgVal + nsPerOpPureGo avgVal ) // run benchmark against different drivers @@ -80,7 +80,7 @@ func Test_BenchmarkSQLite(t *testing.T) { br := testing.Benchmark(func(b *testing.B) { f(b, db) }) // contribue metric to average - nsPerOpBase.contribInt(br.NsPerOp()) + nsPerOpCGo.contribInt(br.NsPerOp()) // close DB if err := db.Close(); err != nil { @@ -92,7 +92,7 @@ func Test_BenchmarkSQLite(t *testing.T) { br = testing.Benchmark(func(b *testing.B) { f(b, db) }) // contribue metric to average - nsPerOp.contribInt(br.NsPerOp()) + nsPerOpPureGo.contribInt(br.NsPerOp()) // close DB if err := db.Close(); err != nil { t.Fatal(err) @@ -101,10 +101,10 @@ func Test_BenchmarkSQLite(t *testing.T) { // print result row fmt.Printf("%-35s | %5.2fx | CGo: %7.3f ms/op | Pure-Go: %7.3f ms/op\n", - getFuncName(f), - nsPerOp.val/nsPerOpBase.val, - nsPerOpBase.val/1e6, - nsPerOp.val/1e6, + toSnakeCase(getFuncName(f)), + nsPerOpPureGo.val/nsPerOpCGo.val, // factor + nsPerOpCGo.val/1e6, // ms/op + nsPerOpPureGo.val/1e6, // ms/op ) } } diff --git a/benchmark/util.go b/benchmark/util.go index 8d239c8..eae83da 100644 --- a/benchmark/util.go +++ b/benchmark/util.go @@ -10,6 +10,7 @@ import ( "math/rand" "path" "reflect" + "regexp" "runtime" "strings" "testing" @@ -26,6 +27,17 @@ const ( testTableName = "t1" ) +var ( + matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") + matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") +) + +func toSnakeCase(str string) string { + snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}") + snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") + return strings.ToLower(snake) +} + // mustExec executes SQL statements and panic if error occurs func mustExec(db *sql.DB, statements ...string) { for _, s := range statements { @@ -133,14 +145,16 @@ func createDB(tb testing.TB, inMemory bool, driverName string) *sql.DB { } db.SetMaxOpenConns(1) - // if !inMemory { - // // disable sync - // _, err = db.Exec(`PRAGMA synchronous = OFF`) - // if err != nil { - // tb.Fatal(err) - // } - // } - + // when in on-disk mode - set synchronous = OFF + // this turns off fsync() sys call at every record inserted out of transaction scope + // thus we don't bother HDD/SSD too often during specific bechmarks + if !inMemory { + // disable sync + _, err = db.Exec(`PRAGMA synchronous = OFF`) + if err != nil { + tb.Fatal(err) + } + } return db } @@ -193,11 +207,6 @@ func pronounceNum(n uint32) string { panic("must have returned already") } -// fracDiv does a fractional division of 2 integers -func fracDiv(x, y int64) float64 { - return float64(x) / float64(y) -} - // AvgVal provides average value with value contributions on-the-fly type avgVal struct { // the current average value @@ -208,7 +217,9 @@ type avgVal struct { } func (a *avgVal) contribFloat(v float64) { - a.val = (a.val*float64(a.numContributions) + v) / (float64(a.numContributions) + 1.) + nContrib := float64(a.numContributions) + a.val = (a.val*nContrib + v) / (nContrib + 1.) + a.numContributions++ } func (a *avgVal) contribInt(v int64) { diff --git a/benchmark/util_test.go b/benchmark/util_test.go index f190bcc..ab3a626 100644 --- a/benchmark/util_test.go +++ b/benchmark/util_test.go @@ -9,15 +9,7 @@ import ( "testing" ) -func test_pronounceNum(t *testing.T) { - // this is only for visual testing - for i := 0; i < 10; i++ { - n := rand.Int31() - t.Logf("%d: %s\n", n, pronounceNum(uint32(n))) - } -} - -func Benchmark_pronounceNum(b *testing.B) { +func BenchmarkPronounceNum(b *testing.B) { for i := 0; i < b.N; i++ { n := rand.Int31() pronounceNum(uint32(n))