From e1e711dc91bb0145304c7c7aac84f5d0cda50e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Garcia?= Date: Fri, 22 Jan 2021 21:25:14 -0300 Subject: [PATCH] Add a Benchmark comparing us with the sqlx package --- Makefile | 5 + README.md | 22 +++++ benchmark_test.go | 232 ++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 5 + kiss_orm_test.go | 1 - 6 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 benchmark_test.go diff --git a/Makefile b/Makefile index 8330824..a787259 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,14 @@ path=./... GOPATH=$(shell go env GOPATH) +TIME=1s + test: setup $(GOPATH)/bin/richgo test $(path) $(args) +bench: + go test -bench=. -benchtime=$(TIME) + lint: setup @$(GOPATH)/bin/golint -set_exit_status -min_confidence 0.9 $(path) $(args) @go vet $(path) $(args) diff --git a/README.md b/README.md index c4e6a2d..7ffb4b3 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Currently we only support 2 Drivers: ### Why KissORM? +> Note: If you want numbers see our Benchmark section below + KissORM was created to fill a hole between the complexity we find in the tools I've seen so far, namely: @@ -200,6 +202,26 @@ This library has a few helper functions for helping your tests: If you want to see examples (we have examples for all the public functions) just read the example tests available on our [example service](./examples/example_service) +### Benchmark Comparison + +The benchmark is not bad, as far the code is in average as fast as sqlx: + +```bash +$ make bench TIME=3s +go test -bench=. -benchtime=3s +goos: linux +goarch: amd64 +pkg: github.com/vingarcia/kissorm +BenchmarkInsert/kissorm-setup/insert-one-4 4306 880132 ns/op +BenchmarkInsert/sqlx-setup/insert-one-4 4573 792488 ns/op +BenchmarkQuery/kissorm-setup/single-row-4 10000 315328 ns/op +BenchmarkQuery/kissorm-setup/multiple-rows-4 9288 388538 ns/op +BenchmarkQuery/sqlx-setup/single-row-4 10000 323424 ns/op +BenchmarkQuery/sqlx-setup/multiple-rows-4 10000 338570 ns/op +PASS +ok github.com/vingarcia/kissorm 21.740s +``` + ### TODO List - Allow the ID field to have a different name diff --git a/benchmark_test.go b/benchmark_test.go new file mode 100644 index 0000000..3ce4f42 --- /dev/null +++ b/benchmark_test.go @@ -0,0 +1,232 @@ +package kissorm_test + +import ( + "context" + "database/sql" + "fmt" + "strconv" + "testing" + + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" + "github.com/vingarcia/kissorm" +) + +func BenchmarkInsert(b *testing.B) { + ctx := context.Background() + + driver := "postgres" + connStr := "host=localhost port=5432 user=postgres password=postgres dbname=kissorm sslmode=disable" + + kissormDB, err := kissorm.New(driver, connStr, 1, "users") + if err != nil { + b.FailNow() + } + + type User struct { + ID int `kissorm:"id" db:"id"` + Name string `kissorm:"name" db:"name"` + Age int `kissorm:"age" db:"age"` + } + + b.Run("kissorm-setup", func(b *testing.B) { + err := recreateTable(connStr) + if err != nil { + b.Fatalf("error creating table: %s", err.Error()) + } + + b.Run("insert-one", func(b *testing.B) { + for i := 0; i < b.N; i++ { + err := kissormDB.Insert(ctx, &User{ + Name: strconv.Itoa(i), + Age: i, + }) + if err != nil { + b.Fatalf("insert error: %s", err.Error()) + } + } + }) + }) + + sqlxDB, err := sqlx.Open(driver, connStr) + sqlxDB.SetMaxOpenConns(1) + + b.Run("sqlx-setup", func(b *testing.B) { + err := recreateTable(connStr) + if err != nil { + b.Fatalf("error creating table: %s", err.Error()) + } + + query := `INSERT INTO users(name, age) VALUES (:name, :age) RETURNING id` + b.Run("insert-one", func(b *testing.B) { + for i := 0; i < b.N; i++ { + user := User{ + Name: strconv.Itoa(i), + Age: i, + } + rows, err := sqlxDB.NamedQuery(query, user) + if err != nil { + b.Fatalf("insert error: %s", err.Error()) + } + if !rows.Next() { + b.Fatalf("missing id from inserted record") + } + rows.Scan(&user.ID) + err = rows.Close() + if err != nil { + b.Fatalf("error closing rows") + } + } + }) + }) +} + +func BenchmarkQuery(b *testing.B) { + ctx := context.Background() + + driver := "postgres" + connStr := "host=localhost port=5432 user=postgres password=postgres dbname=kissorm sslmode=disable" + + kissormDB, err := kissorm.New(driver, connStr, 1, "users") + if err != nil { + b.FailNow() + } + + type User struct { + ID int `kissorm:"id" db:"id"` + Name string `kissorm:"name" db:"name"` + Age int `kissorm:"age" db:"age"` + } + + b.Run("kissorm-setup", func(b *testing.B) { + err := recreateTable(connStr) + if err != nil { + b.Fatalf("error creating table: %s", err.Error()) + } + + err = insertUsers(connStr, 100) + if err != nil { + b.Fatalf("error inserting users: %s", err.Error()) + } + + b.Run("single-row", func(b *testing.B) { + for i := 0; i < b.N; i++ { + var user User + err := kissormDB.QueryOne(ctx, &user, `SELECT * FROM users OFFSET $1 LIMIT 1`, i%100) + if err != nil { + b.Fatalf("query error: %s", err.Error()) + } + } + }) + + b.Run("multiple-rows", func(b *testing.B) { + for i := 0; i < b.N; i++ { + var users []User + err := kissormDB.Query(ctx, &users, `SELECT * FROM users OFFSET $1 LIMIT 10`, i%90) + if err != nil { + b.Fatalf("query error: %s", err.Error()) + } + if len(users) < 10 { + b.Fatalf("expected 10 scanned users, but got: %d", len(users)) + } + } + }) + }) + + sqlxDB, err := sqlx.Open(driver, connStr) + sqlxDB.SetMaxOpenConns(1) + + b.Run("sqlx-setup", func(b *testing.B) { + err := recreateTable(connStr) + if err != nil { + b.Fatalf("error creating table: %s", err.Error()) + } + + err = insertUsers(connStr, 100) + if err != nil { + b.Fatalf("error inserting users: %s", err.Error()) + } + + b.Run("single-row", func(b *testing.B) { + for i := 0; i < b.N; i++ { + var user User + rows, err := sqlxDB.Queryx(`SELECT * FROM users OFFSET $1 LIMIT 1`, i%100) + if err != nil { + b.Fatalf("insert error: %s", err.Error()) + } + if !rows.Next() { + b.Fatalf("missing user from inserted record, offset: %d", i%100) + } + rows.StructScan(&user) + err = rows.Close() + if err != nil { + b.Fatalf("error closing rows") + } + } + }) + + b.Run("multiple-rows", func(b *testing.B) { + for i := 0; i < b.N; i++ { + var users []User + rows, err := sqlxDB.Queryx(`SELECT * FROM users OFFSET $1 LIMIT 10`, i%90) + if err != nil { + b.Fatalf("insert error: %s", err.Error()) + } + for j := 0; j < 10; j++ { + if !rows.Next() { + b.Fatalf("missing user from inserted record, offset: %d", i%100) + } + var user User + rows.StructScan(&user) + users = append(users, user) + } + if len(users) < 10 { + b.Fatalf("expected 10 scanned users, but got: %d", len(users)) + } + + err = rows.Close() + if err != nil { + b.Fatalf("error closing rows") + } + } + }) + }) +} + +func recreateTable(connStr string) error { + db, err := sql.Open("postgres", connStr) + if err != nil { + return err + } + defer db.Close() + + db.Exec(`DROP TABLE users`) + + _, err = db.Exec(`CREATE TABLE users ( + id serial PRIMARY KEY, + age INT, + name VARCHAR(50) + )`) + if err != nil { + return fmt.Errorf("failed to create new users table: %s", err.Error()) + } + + return nil +} + +func insertUsers(connStr string, numUsers int) error { + db, err := sql.Open("postgres", connStr) + if err != nil { + return err + } + defer db.Close() + + for i := 0; i < numUsers; i++ { + _, err = db.Exec(`INSERT INTO users (name, age) VALUES ($1, $2)`, strconv.Itoa(i), i) + if err != nil { + return fmt.Errorf("failed to insert new user: %s", err.Error()) + } + } + + return nil +} diff --git a/go.mod b/go.mod index 97b4a6a..c76966a 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.14 require ( github.com/ditointernet/go-assert v0.0.0-20200120164340-9e13125a7018 github.com/golang/mock v1.4.4 + github.com/jmoiron/sqlx v1.2.0 github.com/lib/pq v1.1.1 github.com/mattn/go-sqlite3 v1.14.6 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 2d374e5..402e456 100644 --- a/go.sum +++ b/go.sum @@ -4,11 +4,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ditointernet/go-assert v0.0.0-20200120164340-9e13125a7018 h1:QsFkVafcKOaZoAB4WcyUHdkPbwh+VYwZgYJb/rU6EIM= github.com/ditointernet/go-assert v0.0.0-20200120164340-9e13125a7018/go.mod h1:5C3SWkut69TSdkerzRDxXMRM5x73PGWNcRLe/xKjXhs= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/kiss_orm_test.go b/kiss_orm_test.go index 287546e..370461c 100644 --- a/kiss_orm_test.go +++ b/kiss_orm_test.go @@ -844,7 +844,6 @@ func TestTransaction(t *testing.T) { _ = c.Insert(ctx, &u2) err = c.Transaction(ctx, func(db ORMProvider) error { - fmt.Printf("received db client: %#v\n", db) err = db.Insert(ctx, &User{Name: "User3"}) assert.Equal(t, nil, err) err = db.Insert(ctx, &User{Name: "User4"})