qb: benchmark
This commit is contained in:
38
README.md
38
README.md
@@ -69,3 +69,41 @@ p := &Person{
|
|||||||
```
|
```
|
||||||
|
|
||||||
For more details see [example test](https://github.com/scylladb/gocqlx/blob/master/example_test.go).
|
For more details see [example test](https://github.com/scylladb/gocqlx/blob/master/example_test.go).
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
Gocqlx is fast, below is a benchmark result comparing `gocqlx` to raw `gocql` on
|
||||||
|
my machine, see the benchmark [here](https://github.com/scylladb/gocqlx/blob/master/benchmark_test.go).
|
||||||
|
|
||||||
|
For query binding gocqlx is faster as it does not require parameter rewriting
|
||||||
|
while binding. For get and insert the performance is comparable.
|
||||||
|
|
||||||
|
```
|
||||||
|
BenchmarkE2EGocqlInsert-4 1000 1580420 ns/op 2624 B/op 59 allocs/op
|
||||||
|
BenchmarkE2EGocqlxInsert-4 2000 648769 ns/op 1557 B/op 34 allocs/op
|
||||||
|
BenchmarkE2EGocqlGet-4 3000 664618 ns/op 1086 B/op 29 allocs/op
|
||||||
|
BenchmarkE2EGocqlxGet-4 3000 631415 ns/op 1440 B/op 32 allocs/op
|
||||||
|
BenchmarkE2EGocqlSelect-4 50 35646283 ns/op 34072 B/op 922 allocs/op
|
||||||
|
BenchmarkE2EGocqlxSelect-4 50 37128897 ns/op 28304 B/op 933 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
Gocqlx comes with automatic snake case support for field names and does not
|
||||||
|
require manual tagging. This is also fast, below is a comparison to
|
||||||
|
`strings.ToLower` function (`sqlx` default).
|
||||||
|
|
||||||
|
```
|
||||||
|
BenchmarkSnakeCase-4 10000000 124 ns/op 32 B/op 2 allocs/op
|
||||||
|
BenchmarkToLower-4 100000000 57.9 ns/op 0 B/op 0 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
Building queries is fast and low on allocations too.
|
||||||
|
|
||||||
|
```
|
||||||
|
BenchmarkCmp-4 3000000 464 ns/op 112 B/op 3 allocs/op
|
||||||
|
BenchmarkDeleteBuilder-4 10000000 214 ns/op 112 B/op 2 allocs/op
|
||||||
|
BenchmarkInsertBuilder-4 20000000 103 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkSelectBuilder-4 10000000 214 ns/op 112 B/op 2 allocs/op
|
||||||
|
BenchmarkUpdateBuilder-4 10000000 212 ns/op 112 B/op 2 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
Enyoy!
|
||||||
|
|||||||
239
benchmark_test.go
Normal file
239
benchmark_test.go
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
|
package gocqlx_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
"github.com/scylladb/gocqlx"
|
||||||
|
"github.com/scylladb/gocqlx/qb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type benchPerson struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
Email []string `json:"email"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
IPAddress string `json:"ip_address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var benchPersonSchema = `
|
||||||
|
CREATE TABLE IF NOT EXISTS gocqlx_test.bench_person (
|
||||||
|
id int,
|
||||||
|
first_name text,
|
||||||
|
last_name text,
|
||||||
|
email list<text>,
|
||||||
|
gender text,
|
||||||
|
ip_address text,
|
||||||
|
PRIMARY KEY(id)
|
||||||
|
)`
|
||||||
|
|
||||||
|
var benchPersonCols = []string{"id", "first_name", "last_name", "email", "gender", "ip_address"}
|
||||||
|
|
||||||
|
func loadFixtures() []*benchPerson {
|
||||||
|
f, err := os.Open("test-fixtures/people.json")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var v []*benchPerson
|
||||||
|
if err := json.NewDecoder(f).Decode(&v); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Insert
|
||||||
|
//
|
||||||
|
|
||||||
|
// BenchmarkE2EGocqlInsert performs standard insert.
|
||||||
|
func BenchmarkE2EGocqlInsert(b *testing.B) {
|
||||||
|
people := loadFixtures()
|
||||||
|
session := createSession(b)
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
if err := createTable(session, benchPersonSchema); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, _ := qb.Insert("gocqlx_test.bench_person").Columns(benchPersonCols...).ToCql()
|
||||||
|
q := session.Query(stmt)
|
||||||
|
defer q.Release()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// prepare
|
||||||
|
p := people[i%len(people)]
|
||||||
|
if err := q.Bind(p.ID, p.FirstName, p.LastName, p.Email, p.Gender, p.IPAddress).Exec(); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
// insert
|
||||||
|
if err := q.Exec(); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkE2EGocqlInsert performs insert with struct binding.
|
||||||
|
func BenchmarkE2EGocqlxInsert(b *testing.B) {
|
||||||
|
people := loadFixtures()
|
||||||
|
session := createSession(b)
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
if err := createTable(session, benchPersonSchema); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, names := qb.Insert("gocqlx_test.bench_person").Columns(benchPersonCols...).ToCql()
|
||||||
|
q := gocqlx.Query(session.Query(stmt), names)
|
||||||
|
defer q.Release()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// prepare
|
||||||
|
p := people[i%len(people)]
|
||||||
|
if err := q.BindStruct(p); err != nil {
|
||||||
|
b.Fatal("bind:", err)
|
||||||
|
}
|
||||||
|
// insert
|
||||||
|
if err := q.Exec(); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Get
|
||||||
|
//
|
||||||
|
|
||||||
|
// BenchmarkE2EGocqlGet performs standard scan.
|
||||||
|
func BenchmarkE2EGocqlGet(b *testing.B) {
|
||||||
|
people := loadFixtures()
|
||||||
|
session := createSession(b)
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
initTable(b, session, people)
|
||||||
|
|
||||||
|
stmt, _ := qb.Select("gocqlx_test.bench_person").Columns(benchPersonCols...).Where(qb.Eq("id")).Limit(1).ToCql()
|
||||||
|
var p benchPerson
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// prepare
|
||||||
|
q := session.Query(stmt)
|
||||||
|
q.Bind(people[i%len(people)].ID)
|
||||||
|
// scan
|
||||||
|
if err := q.Scan(&p.ID, &p.FirstName, &p.LastName, &p.Email, &p.Gender, &p.IPAddress); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
// release
|
||||||
|
q.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkE2EGocqlxGet performs get.
|
||||||
|
func BenchmarkE2EGocqlxGet(b *testing.B) {
|
||||||
|
people := loadFixtures()
|
||||||
|
session := createSession(b)
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
initTable(b, session, people)
|
||||||
|
|
||||||
|
stmt, _ := qb.Select("gocqlx_test.bench_person").Columns(benchPersonCols...).Where(qb.Eq("id")).Limit(1).ToCql()
|
||||||
|
var p benchPerson
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// prepare
|
||||||
|
q := session.Query(stmt)
|
||||||
|
q.Bind(people[i%len(people)].ID)
|
||||||
|
// get
|
||||||
|
gocqlx.Get(&p, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Select
|
||||||
|
//
|
||||||
|
|
||||||
|
// BenchmarkE2EGocqlSelect performs standard loop scan.
|
||||||
|
func BenchmarkE2EGocqlSelect(b *testing.B) {
|
||||||
|
people := loadFixtures()
|
||||||
|
session := createSession(b)
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
initTable(b, session, people)
|
||||||
|
|
||||||
|
stmt, _ := qb.Select("gocqlx_test.bench_person").Columns(benchPersonCols...).Limit(100).ToCql()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// prepare
|
||||||
|
v := make([]*benchPerson, 100)
|
||||||
|
q := session.Query(stmt)
|
||||||
|
i := q.Iter()
|
||||||
|
// loop scan
|
||||||
|
p := new(benchPerson)
|
||||||
|
for i.Scan(&p.ID, &p.FirstName, &p.LastName, &p.Email, &p.Gender, &p.IPAddress) {
|
||||||
|
v = append(v, p)
|
||||||
|
p = new(benchPerson)
|
||||||
|
}
|
||||||
|
if err := i.Close(); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
// release
|
||||||
|
q.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkE2EGocqlSelect performs select.
|
||||||
|
func BenchmarkE2EGocqlxSelect(b *testing.B) {
|
||||||
|
people := loadFixtures()
|
||||||
|
session := createSession(b)
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
initTable(b, session, people)
|
||||||
|
|
||||||
|
stmt, _ := qb.Select("gocqlx_test.bench_person").Columns(benchPersonCols...).Limit(100).ToCql()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
// prepare
|
||||||
|
q := session.Query(stmt)
|
||||||
|
var v []*benchPerson
|
||||||
|
// select
|
||||||
|
if err := gocqlx.Select(&v, q); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initTable(b *testing.B, session *gocql.Session, people []*benchPerson) {
|
||||||
|
if err := createTable(session, benchPersonSchema); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, names := qb.Insert("gocqlx_test.bench_person").Columns(benchPersonCols...).ToCql()
|
||||||
|
q := gocqlx.Query(session.Query(stmt), names)
|
||||||
|
|
||||||
|
for _, p := range people {
|
||||||
|
if err := q.BindStruct(p); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := q.Exec(); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,7 +72,6 @@ func createKeyspace(tb testing.TB, cluster *gocql.ClusterConfig, keyspace string
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
defer tb.Log("closing keyspace session")
|
|
||||||
|
|
||||||
err = createTable(session, `DROP KEYSPACE IF EXISTS `+keyspace)
|
err = createTable(session, `DROP KEYSPACE IF EXISTS `+keyspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var personSchema = `
|
var personSchema = `
|
||||||
CREATE TABLE gocqlx_test.person (
|
CREATE TABLE IF NOT EXISTS gocqlx_test.person (
|
||||||
first_name text,
|
first_name text,
|
||||||
last_name text,
|
last_name text,
|
||||||
email list<text>,
|
email list<text>,
|
||||||
@@ -19,7 +19,7 @@ CREATE TABLE gocqlx_test.person (
|
|||||||
)`
|
)`
|
||||||
|
|
||||||
var placeSchema = `
|
var placeSchema = `
|
||||||
CREATE TABLE gocqlx_test.place (
|
CREATE TABLE IF NOT EXISTS gocqlx_test.place (
|
||||||
country text,
|
country text,
|
||||||
city text,
|
city text,
|
||||||
code int,
|
code int,
|
||||||
@@ -78,7 +78,7 @@ func TestExample(t *testing.T) {
|
|||||||
// Query the database, storing results in a []Person (wrapped in []interface{}).
|
// Query the database, storing results in a []Person (wrapped in []interface{}).
|
||||||
{
|
{
|
||||||
var people []Person
|
var people []Person
|
||||||
if err := gocqlx.Select(&people, session.Query("SELECT * FROM person")); err != nil {
|
if err := gocqlx.Select(&people, session.Query("SELECT * FROM gocqlx_test.person")); err != nil {
|
||||||
t.Fatal("select:", err)
|
t.Fatal("select:", err)
|
||||||
}
|
}
|
||||||
t.Log(people)
|
t.Log(people)
|
||||||
@@ -89,7 +89,7 @@ func TestExample(t *testing.T) {
|
|||||||
// Get a single result.
|
// Get a single result.
|
||||||
{
|
{
|
||||||
var jason Person
|
var jason Person
|
||||||
if err := gocqlx.Get(&jason, session.Query("SELECT * FROM person WHERE first_name=?", "Jason")); err != nil {
|
if err := gocqlx.Get(&jason, session.Query("SELECT * FROM gocqlx_test.person WHERE first_name=?", "Jason")); err != nil {
|
||||||
t.Fatal("get:", err)
|
t.Fatal("get:", err)
|
||||||
}
|
}
|
||||||
t.Log(jason)
|
t.Log(jason)
|
||||||
@@ -100,7 +100,7 @@ func TestExample(t *testing.T) {
|
|||||||
// Loop through rows using only one struct.
|
// Loop through rows using only one struct.
|
||||||
{
|
{
|
||||||
var place Place
|
var place Place
|
||||||
iter := gocqlx.Iter(session.Query("SELECT * FROM place"))
|
iter := gocqlx.Iter(session.Query("SELECT * FROM gocqlx_test.place"))
|
||||||
for iter.StructScan(&place) {
|
for iter.StructScan(&place) {
|
||||||
t.Log(place)
|
t.Log(place)
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ func TestExample(t *testing.T) {
|
|||||||
|
|
||||||
// Insert
|
// Insert
|
||||||
{
|
{
|
||||||
q := Query(qb.Insert("person").Columns("first_name", "last_name", "email").ToCql())
|
q := Query(qb.Insert("gocqlx_test.person").Columns("first_name", "last_name", "email").ToCql())
|
||||||
if err := q.BindStruct(p); err != nil {
|
if err := q.BindStruct(p); err != nil {
|
||||||
t.Fatal("bind:", err)
|
t.Fatal("bind:", err)
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ func TestExample(t *testing.T) {
|
|||||||
{
|
{
|
||||||
p.Email = append(p.Email, "patricia1.citzen@gocqlx_test.com")
|
p.Email = append(p.Email, "patricia1.citzen@gocqlx_test.com")
|
||||||
|
|
||||||
q := Query(qb.Update("person").Set("email").Where(qb.Eq("first_name"), qb.Eq("last_name")).ToCql())
|
q := Query(qb.Update("gocqlx_test.person").Set("email").Where(qb.Eq("first_name"), qb.Eq("last_name")).ToCql())
|
||||||
if err := q.BindStruct(p); err != nil {
|
if err := q.BindStruct(p); err != nil {
|
||||||
t.Fatal("bind:", err)
|
t.Fatal("bind:", err)
|
||||||
}
|
}
|
||||||
@@ -147,7 +147,7 @@ func TestExample(t *testing.T) {
|
|||||||
|
|
||||||
// Select
|
// Select
|
||||||
{
|
{
|
||||||
q := Query(qb.Select("person").Where(qb.In("first_name")).ToCql())
|
q := Query(qb.Select("gocqlx_test.person").Where(qb.In("first_name")).ToCql())
|
||||||
m := map[string]interface{}{
|
m := map[string]interface{}{
|
||||||
"first_name": []string{"Patricia", "John"},
|
"first_name": []string{"Patricia", "John"},
|
||||||
}
|
}
|
||||||
@@ -168,7 +168,7 @@ func TestExample(t *testing.T) {
|
|||||||
// Named queries, using `:name` as the bindvar.
|
// Named queries, using `:name` as the bindvar.
|
||||||
{
|
{
|
||||||
// compile query to valid gocqlx query and list of named parameters
|
// compile query to valid gocqlx query and list of named parameters
|
||||||
stmt, names, err := gocqlx.CompileNamedQuery([]byte("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)"))
|
stmt, names, err := gocqlx.CompileNamedQuery([]byte("INSERT INTO gocqlx_test.person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("compile:", err)
|
t.Fatal("compile:", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,16 +69,12 @@ func TestSnakeCase(t *testing.T) {
|
|||||||
|
|
||||||
func BenchmarkSnakeCase(b *testing.B) {
|
func BenchmarkSnakeCase(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for _, test := range snakeTable {
|
snakeCase(snakeTable[b.N%len(snakeTable)].N)
|
||||||
snakeCase(test.N)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkToLower(b *testing.B) {
|
func BenchmarkToLower(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for _, test := range snakeTable {
|
strings.ToLower(snakeTable[b.N%len(snakeTable)].N)
|
||||||
strings.ToLower(test.N)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user