From e439372d2a8885e66c15db10a5269521b5597dc3 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Thu, 7 May 2020 21:20:37 -0500 Subject: [PATCH] Add multi-row select benchmarks --- bench_test.go | 315 +++++++++++++++++++++++++++++++++++ stdlib/bench_test.go | 109 ++++++++++++ stdlib/sql_test.go | 4 +- stdlib/stdlibutil110_test.go | 2 +- 4 files changed, 427 insertions(+), 3 deletions(-) create mode 100644 stdlib/bench_test.go diff --git a/bench_test.go b/bench_test.go index f50677b7..890d6fb5 100644 --- a/bench_test.go +++ b/bench_test.go @@ -949,3 +949,318 @@ func BenchmarkSelectManyRegisteredEnum(b *testing.B) { } } } + +func getSelectRowsCounts(b *testing.B) []int64 { + var rowCounts []int64 + { + s := os.Getenv("PGX_BENCH_SELECT_ROWS_COUNTS") + if s != "" { + for _, p := range strings.Split(s, " ") { + n, err := strconv.ParseInt(p, 10, 64) + if err != nil { + b.Fatalf("Bad PGX_BENCH_SELECT_ROWS_COUNTS value: %v", err) + } + rowCounts = append(rowCounts, n) + } + } + } + + if len(rowCounts) == 0 { + rowCounts = []int64{1, 10, 100, 1000} + } + + return rowCounts +} + +type BenchRowSimple struct { + ID int32 + FirstName string + LastName string + Sex string + BirthDate time.Time + Weight int32 + Height int32 + UpdateTime time.Time +} + +func BenchmarkSelectRowsScanSimple(b *testing.B) { + conn := mustConnectString(b, os.Getenv("PGX_TEST_DATABASE")) + defer closeConn(b, conn) + + rowCounts := getSelectRowsCounts(b) + + for _, rowCount := range rowCounts { + b.Run(fmt.Sprintf("%d rows", rowCount), func(b *testing.B) { + br := &BenchRowSimple{} + for i := 0; i < b.N; i++ { + rows, err := conn.Query(context.Background(), "select n, 'Adam', 'Smith ' || n, 'male', '1952-06-16'::date, 258, 72, now() from generate_series(1, $1) n", rowCount) + if err != nil { + b.Fatal(err) + } + + for rows.Next() { + rows.Scan(&br.ID, &br.FirstName, &br.LastName, &br.Sex, &br.BirthDate, &br.Weight, &br.Height, &br.UpdateTime) + } + + if rows.Err() != nil { + b.Fatal(rows.Err()) + } + } + }) + } +} + +type BenchRowStringBytes struct { + ID int32 + FirstName []byte + LastName []byte + Sex []byte + BirthDate time.Time + Weight int32 + Height int32 + UpdateTime time.Time +} + +func BenchmarkSelectRowsScanStringBytes(b *testing.B) { + conn := mustConnectString(b, os.Getenv("PGX_TEST_DATABASE")) + defer closeConn(b, conn) + + rowCounts := getSelectRowsCounts(b) + + for _, rowCount := range rowCounts { + b.Run(fmt.Sprintf("%d rows", rowCount), func(b *testing.B) { + br := &BenchRowStringBytes{} + for i := 0; i < b.N; i++ { + rows, err := conn.Query(context.Background(), "select n, 'Adam', 'Smith ' || n, 'male', '1952-06-16'::date, 258, 72, now() from generate_series(1, $1) n", rowCount) + if err != nil { + b.Fatal(err) + } + + for rows.Next() { + rows.Scan(&br.ID, &br.FirstName, &br.LastName, &br.Sex, &br.BirthDate, &br.Weight, &br.Height, &br.UpdateTime) + } + + if rows.Err() != nil { + b.Fatal(rows.Err()) + } + } + }) + } +} + +type BenchRowDecoder struct { + ID pgtype.Int4 + FirstName pgtype.Text + LastName pgtype.Text + Sex pgtype.Text + BirthDate pgtype.Date + Weight pgtype.Int4 + Height pgtype.Int4 + UpdateTime pgtype.Timestamptz +} + +func BenchmarkSelectRowsScanBinaryDecoder(b *testing.B) { + conn := mustConnectString(b, os.Getenv("PGX_TEST_DATABASE")) + defer closeConn(b, conn) + + rowCounts := getSelectRowsCounts(b) + + for _, rowCount := range rowCounts { + b.Run(fmt.Sprintf("%d rows", rowCount), func(b *testing.B) { + br := &BenchRowDecoder{} + for i := 0; i < b.N; i++ { + rows, err := conn.Query(context.Background(), "select n, 'Adam', 'Smith ' || n, 'male', '1952-06-16'::date, 258, 72, now() from generate_series(1, $1) n", rowCount) + if err != nil { + b.Fatal(err) + } + + for rows.Next() { + rows.Scan(&br.ID, &br.FirstName, &br.LastName, &br.Sex, &br.BirthDate, &br.Weight, &br.Height, &br.UpdateTime) + } + + if rows.Err() != nil { + b.Fatal(rows.Err()) + } + } + }) + } +} + +func BenchmarkSelectRowsExplicitBinaryDecoding(b *testing.B) { + conn := mustConnectString(b, os.Getenv("PGX_TEST_DATABASE")) + defer closeConn(b, conn) + + rowCounts := getSelectRowsCounts(b) + + for _, rowCount := range rowCounts { + b.Run(fmt.Sprintf("%d rows", rowCount), func(b *testing.B) { + br := &BenchRowDecoder{} + for i := 0; i < b.N; i++ { + rows, err := conn.Query(context.Background(), "select n, 'Adam', 'Smith ' || n, 'male', '1952-06-16'::date, 258, 72, now() from generate_series(1, $1) n", rowCount) + if err != nil { + b.Fatal(err) + } + + for rows.Next() { + rawValues := rows.RawValues() + + err = br.ID.DecodeBinary(conn.ConnInfo(), rawValues[0]) + if err != nil { + b.Fatal(err) + } + + err = br.FirstName.DecodeBinary(conn.ConnInfo(), rawValues[1]) + if err != nil { + b.Fatal(err) + } + + err = br.LastName.DecodeBinary(conn.ConnInfo(), rawValues[2]) + if err != nil { + b.Fatal(err) + } + + err = br.Sex.DecodeBinary(conn.ConnInfo(), rawValues[3]) + if err != nil { + b.Fatal(err) + } + + err = br.BirthDate.DecodeBinary(conn.ConnInfo(), rawValues[4]) + if err != nil { + b.Fatal(err) + } + + err = br.Weight.DecodeBinary(conn.ConnInfo(), rawValues[5]) + if err != nil { + b.Fatal(err) + } + + err = br.Height.DecodeBinary(conn.ConnInfo(), rawValues[6]) + if err != nil { + b.Fatal(err) + } + + err = br.UpdateTime.DecodeBinary(conn.ConnInfo(), rawValues[7]) + if err != nil { + b.Fatal(err) + } + } + + if rows.Err() != nil { + b.Fatal(rows.Err()) + } + } + }) + } +} + +func BenchmarkSelectRowsPgConnExecText(b *testing.B) { + conn := mustConnectString(b, os.Getenv("PGX_TEST_DATABASE")) + defer closeConn(b, conn) + + rowCounts := getSelectRowsCounts(b) + + for _, rowCount := range rowCounts { + b.Run(fmt.Sprintf("%d rows", rowCount), func(b *testing.B) { + for i := 0; i < b.N; i++ { + mrr := conn.PgConn().Exec(context.Background(), fmt.Sprintf("select n, 'Adam', 'Smith ' || n, 'male', '1952-06-16'::date, 258, 72, now() from generate_series(1, %d) n", rowCount)) + for mrr.NextResult() { + rr := mrr.ResultReader() + for rr.NextRow() { + rr.Values() + } + } + + err := mrr.Close() + if err != nil { + b.Fatal(err) + } + } + }) + } +} + +func BenchmarkSelectRowsPgConnExecParams(b *testing.B) { + conn := mustConnectString(b, os.Getenv("PGX_TEST_DATABASE")) + defer closeConn(b, conn) + + rowCounts := getSelectRowsCounts(b) + + for _, rowCount := range rowCounts { + b.Run(fmt.Sprintf("%d rows", rowCount), func(b *testing.B) { + formats := []struct { + name string + code int16 + }{ + {"text", pgx.TextFormatCode}, + {"binary", pgx.BinaryFormatCode}, + } + for _, format := range formats { + b.Run(format.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + rr := conn.PgConn().ExecParams( + context.Background(), + "select n, 'Adam', 'Smith ' || n, 'male', '1952-06-16'::date, 258, 72, now() from generate_series(1, $1) n", + [][]byte{[]byte(strconv.FormatInt(rowCount, 10))}, + nil, + nil, + []int16{format.code}, + ) + for rr.NextRow() { + rr.Values() + } + + _, err := rr.Close() + if err != nil { + b.Fatal(err) + } + } + }) + } + }) + } +} + +func BenchmarkSelectRowsPgConnExecPrepared(b *testing.B) { + conn := mustConnectString(b, os.Getenv("PGX_TEST_DATABASE")) + defer closeConn(b, conn) + + rowCounts := getSelectRowsCounts(b) + + _, err := conn.PgConn().Prepare(context.Background(), "ps1", "select n, 'Adam', 'Smith ' || n, 'male', '1952-06-16'::date, 258, 72, now() from generate_series(1, $1) n", nil) + if err != nil { + b.Fatal(err) + } + + for _, rowCount := range rowCounts { + b.Run(fmt.Sprintf("%d rows", rowCount), func(b *testing.B) { + formats := []struct { + name string + code int16 + }{ + {"text", pgx.TextFormatCode}, + {"binary", pgx.BinaryFormatCode}, + } + for _, format := range formats { + b.Run(format.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + rr := conn.PgConn().ExecPrepared( + context.Background(), + "ps1", + [][]byte{[]byte(strconv.FormatInt(rowCount, 10))}, + nil, + []int16{format.code}, + ) + for rr.NextRow() { + rr.Values() + } + + _, err := rr.Close() + if err != nil { + b.Fatal(err) + } + } + }) + } + }) + } +} diff --git a/stdlib/bench_test.go b/stdlib/bench_test.go new file mode 100644 index 00000000..8eba2ca1 --- /dev/null +++ b/stdlib/bench_test.go @@ -0,0 +1,109 @@ +package stdlib_test + +import ( + "database/sql" + "fmt" + "os" + "strconv" + "strings" + "testing" + "time" +) + +func getSelectRowsCounts(b *testing.B) []int64 { + var rowCounts []int64 + { + s := os.Getenv("PGX_BENCH_SELECT_ROWS_COUNTS") + if s != "" { + for _, p := range strings.Split(s, " ") { + n, err := strconv.ParseInt(p, 10, 64) + if err != nil { + b.Fatalf("Bad PGX_BENCH_SELECT_ROWS_COUNTS value: %v", err) + } + rowCounts = append(rowCounts, n) + } + } + } + + if len(rowCounts) == 0 { + rowCounts = []int64{1, 10, 100, 1000} + } + + return rowCounts +} + +type BenchRowSimple struct { + ID int32 + FirstName string + LastName string + Sex string + BirthDate time.Time + Weight int32 + Height int32 + UpdateTime time.Time +} + +func BenchmarkSelectRowsScanSimple(b *testing.B) { + db := openDB(b) + defer closeDB(b, db) + + rowCounts := getSelectRowsCounts(b) + + for _, rowCount := range rowCounts { + b.Run(fmt.Sprintf("%d rows", rowCount), func(b *testing.B) { + br := &BenchRowSimple{} + for i := 0; i < b.N; i++ { + rows, err := db.Query("select n, 'Adam', 'Smith ' || n, 'male', '1952-06-16'::date, 258, 72, now() from generate_series(1, $1) n", rowCount) + if err != nil { + b.Fatal(err) + } + + for rows.Next() { + rows.Scan(&br.ID, &br.FirstName, &br.LastName, &br.Sex, &br.BirthDate, &br.Weight, &br.Height, &br.UpdateTime) + } + + if rows.Err() != nil { + b.Fatal(rows.Err()) + } + } + }) + } +} + +type BenchRowNull struct { + ID sql.NullInt32 + FirstName sql.NullString + LastName sql.NullString + Sex sql.NullString + BirthDate sql.NullTime + Weight sql.NullInt32 + Height sql.NullInt32 + UpdateTime sql.NullTime +} + +func BenchmarkSelectRowsScanNull(b *testing.B) { + db := openDB(b) + defer closeDB(b, db) + + rowCounts := getSelectRowsCounts(b) + + for _, rowCount := range rowCounts { + b.Run(fmt.Sprintf("%d rows", rowCount), func(b *testing.B) { + br := &BenchRowSimple{} + for i := 0; i < b.N; i++ { + rows, err := db.Query("select n, 'Adam', 'Smith ' || n, 'male', '1952-06-16'::date, 258, 72, now() from generate_series(1, $1) n", rowCount) + if err != nil { + b.Fatal(err) + } + + for rows.Next() { + rows.Scan(&br.ID, &br.FirstName, &br.LastName, &br.Sex, &br.BirthDate, &br.Weight, &br.Height, &br.UpdateTime) + } + + if rows.Err() != nil { + b.Fatal(rows.Err()) + } + } + }) + } +} diff --git a/stdlib/sql_test.go b/stdlib/sql_test.go index ad9b6485..d387f25b 100644 --- a/stdlib/sql_test.go +++ b/stdlib/sql_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/require" ) -func closeDB(t *testing.T, db *sql.DB) { +func closeDB(t testing.TB, db *sql.DB) { err := db.Close() if err != nil { t.Fatalf("db.Close unexpectedly failed: %v", err) @@ -27,7 +27,7 @@ func closeDB(t *testing.T, db *sql.DB) { } // Do a simple query to ensure the connection is still usable -func ensureConnValid(t *testing.T, db *sql.DB) { +func ensureConnValid(t testing.TB, db *sql.DB) { var sum, rowCount int32 rows, err := db.Query("select generate_series(1,$1)", 10) diff --git a/stdlib/stdlibutil110_test.go b/stdlib/stdlibutil110_test.go index fea4e2c4..6eec51e0 100644 --- a/stdlib/stdlibutil110_test.go +++ b/stdlib/stdlibutil110_test.go @@ -11,7 +11,7 @@ import ( "github.com/jackc/pgx/v4/stdlib" ) -func openDB(t *testing.T) *sql.DB { +func openDB(t testing.TB) *sql.DB { config, err := pgx.ParseConfig(os.Getenv("PGX_TEST_DATABASE")) if err != nil { t.Fatalf("pgx.ParseConnectionString failed: %v", err)