mirror of
https://github.com/glebarez/go-sqlite.git
synced 2025-10-09 17:50:03 +08:00
new benchmarks
This commit is contained in:
@@ -6,6 +6,8 @@ package benchmark
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"path"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@@ -13,6 +15,110 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
// maximum for randomly generated number
|
||||
maxGeneratedNum = 1000000
|
||||
|
||||
// default row count for pre-filled test table
|
||||
testTableRowCount = 100000
|
||||
|
||||
// default name for test table
|
||||
testTableName = "t1"
|
||||
)
|
||||
|
||||
// mustExec executes SQL statements and panic if error occurs
|
||||
func mustExec(db *sql.DB, statements ...string) {
|
||||
for _, s := range statements {
|
||||
if _, err := db.Exec(s); err != nil {
|
||||
panic(fmt.Sprintf("%s: %v", s, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// createTestTableWithName creates new DB table
|
||||
// with following DDL:
|
||||
//
|
||||
// CREATE TABLE <tableName>(a INTEGER, b INTEGER, c VARCHAR(100)).
|
||||
//
|
||||
// Additionaly, indicies are created for columns whose names are passed in indexedColumns.
|
||||
func createTestTableWithName(db *sql.DB, tableName string, indexedColumns ...string) {
|
||||
// define table create statements
|
||||
statements := []string{
|
||||
fmt.Sprintf(`DROP TABLE IF EXISTS %s`, tableName),
|
||||
fmt.Sprintf(`CREATE TABLE %s(a INTEGER, b INTEGER, c VARCHAR(100))`, tableName),
|
||||
}
|
||||
|
||||
// add index creating statements
|
||||
for i, indexedColumn := range indexedColumns {
|
||||
statements = append(statements, fmt.Sprintf(`CREATE INDEX i%d ON %s(%s)`, i+1, tableName, indexedColumn))
|
||||
}
|
||||
|
||||
// execute table creation
|
||||
mustExec(db, statements...)
|
||||
}
|
||||
|
||||
// createTestTable is a wrapper for createTestTableWithName with default table name
|
||||
func createTestTable(db *sql.DB, indexedColumns ...string) {
|
||||
createTestTableWithName(db, testTableName, indexedColumns...)
|
||||
}
|
||||
|
||||
// runInTransaction executes f() inside BEGIN/COMMIT block
|
||||
func runInTransaction(db *sql.DB, f func()) {
|
||||
if _, err := db.Exec(`BEGIN`); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f()
|
||||
if _, err := db.Exec(`COMMIT`); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// fillTestTable inserts <rowCount> rows into test table of default name (testTableName).
|
||||
// the values of columns are as follows:
|
||||
//
|
||||
// a - sequence number starting from 1
|
||||
//
|
||||
// b - random number between 0 and maxGeneratedNum
|
||||
//
|
||||
// c - text with english prononciation of b
|
||||
//
|
||||
// for example, SQL statements will be similiar to following:
|
||||
//
|
||||
// INSERT INTO t1 VALUES(1,13153,'thirteen thousand one hundred fifty three');
|
||||
//
|
||||
// INSERT INTO t1 VALUES(2,75560,'seventy five thousand five hundred sixty');
|
||||
func fillTestTable(db *sql.DB, rowCount int) {
|
||||
// prepare statement for insertion of rows
|
||||
stmt, err := db.Prepare(fmt.Sprintf("INSERT INTO %s VALUES(?,?,?)", testTableName))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
// insert rows
|
||||
for i := 0; i < rowCount; i++ {
|
||||
// generate random number
|
||||
num := rand.Int31n(maxGeneratedNum)
|
||||
|
||||
// get number as words
|
||||
numAsWords := pronounceNum(uint32(num))
|
||||
|
||||
// insert row
|
||||
if _, err := stmt.Exec(i+1, num, numAsWords); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fillTestTableInTx calls fillTestTable inside a transaction
|
||||
func fillTestTableInTx(db *sql.DB, rowCount int) {
|
||||
runInTransaction(db, func() {
|
||||
fillTestTable(db, rowCount)
|
||||
})
|
||||
}
|
||||
|
||||
// createDB creates a new instance of sql.DB with specified SQLite driver name.
|
||||
// if inMemory = false, then database file is created in random temporary directory via tb.TempDir()
|
||||
func createDB(tb testing.TB, inMemory bool, driverName string) *sql.DB {
|
||||
var dsn string
|
||||
if inMemory {
|
||||
@@ -23,42 +129,22 @@ func createDB(tb testing.TB, inMemory bool, driverName string) *sql.DB {
|
||||
|
||||
db, err := sql.Open(driverName, dsn)
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
panic(err)
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
// if !inMemory {
|
||||
// // disable sync
|
||||
// _, err = db.Exec(`PRAGMA synchronous = OFF`)
|
||||
// if err != nil {
|
||||
// tb.Fatal(err)
|
||||
// }
|
||||
// }
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func createTestTable(tb testing.TB, db *sql.DB, nRows int) {
|
||||
if _, err := db.Exec("drop table if exists t"); err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := db.Exec("create table t(i int)"); err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
|
||||
if nRows > 0 {
|
||||
s, err := db.Prepare("insert into t values(?)")
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
if _, err := db.Exec("begin"); err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
|
||||
for i := 0; i < nRows; i++ {
|
||||
if _, err := s.Exec(int64(i)); err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
}
|
||||
if _, err := db.Exec("commit"); err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getFuncName gets function name as string, without package prefix
|
||||
func getFuncName(i interface{}) string {
|
||||
// get function name as "package.function"
|
||||
fn := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||
@@ -67,3 +153,64 @@ func getFuncName(i interface{}) string {
|
||||
comps := strings.Split(fn, ".")
|
||||
return comps[len(comps)-1]
|
||||
}
|
||||
|
||||
// pronounceNum generates english pronounciation for a given number
|
||||
func pronounceNum(n uint32) string {
|
||||
switch {
|
||||
case n == 0:
|
||||
return `zero`
|
||||
case n < 10:
|
||||
return []string{`one`, `two`, `three`, `four`, `five`, `six`, `seven`, `eight`, `nine`}[n-1]
|
||||
case n < 20:
|
||||
return []string{`ten`, `eleven`, `twelve`, `thirteen`, `fourteen`, `fifteen`, `sixteen`, `seventeen`, `eighteen`, `nineteen`}[n-10]
|
||||
case n < 100:
|
||||
p := []string{`twenty`, `thirty`, `forty`, `fifty`, `sixty`, `seventy`, `eighty`, `ninety`}[n/10-2]
|
||||
if n%10 == 0 {
|
||||
return p
|
||||
}
|
||||
return fmt.Sprint(p, " ", pronounceNum(n%10))
|
||||
default:
|
||||
divisors := []struct {
|
||||
num uint32
|
||||
name string
|
||||
}{
|
||||
{num: 1000000000, name: `billion`},
|
||||
{num: 1000000, name: `million`},
|
||||
{num: 1000, name: `thousand`},
|
||||
{num: 100, name: `hundred`},
|
||||
}
|
||||
|
||||
for _, div := range divisors {
|
||||
if n >= div.num {
|
||||
p := fmt.Sprint(pronounceNum(n/div.num), " ", div.name)
|
||||
if n%div.num == 0 {
|
||||
return p
|
||||
}
|
||||
return fmt.Sprint(p, " ", pronounceNum(n%div.num))
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
val float64
|
||||
|
||||
// number of contribuitons for involved in current average
|
||||
numContributions int
|
||||
}
|
||||
|
||||
func (a *avgVal) contribFloat(v float64) {
|
||||
a.val = (a.val*float64(a.numContributions) + v) / (float64(a.numContributions) + 1.)
|
||||
}
|
||||
|
||||
func (a *avgVal) contribInt(v int64) {
|
||||
a.contribFloat(float64(v))
|
||||
}
|
||||
|
Reference in New Issue
Block a user