From 90e52378e19cdfacb64f67f2965d2e21cd50a61f Mon Sep 17 00:00:00 2001 From: Roman Atachiants Date: Thu, 15 Jul 2021 00:48:06 +0400 Subject: [PATCH] add bench --- examples/bench/README.md | 43 ++++++++++++ examples/bench/bench.go | 144 +++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + 4 files changed, 190 insertions(+) create mode 100644 examples/bench/README.md create mode 100644 examples/bench/bench.go diff --git a/examples/bench/README.md b/examples/bench/README.md new file mode 100644 index 0000000..631a7f0 --- /dev/null +++ b/examples/bench/README.md @@ -0,0 +1,43 @@ +# Concurrency Benchmark + +This is an example benchmark with various workloads (90% read / 10% write, etc) on a collection of 1 million elements with different goroutine pools. In this example we're combining two types of transactions: + * Read transactions that query a random index and iterate over the results over a single column. + * Write transactions that update a random element (point-write). + +Note that the goal of this benchmark is to validater concurrency, not throughput this represents the current "best" case scenario when the updates are random and do less likely to incur contention. Reads, however quite often would hit the same chunks as only the index itself is randomized. + +``` +90%-10% 1 procs 143,221,213 read/s 70 write/s +90%-10% 8 procs 1,081,511,102 read/s 483 write/s +90%-10% 16 procs 1,068,562,727 read/s 455 write/s +90%-10% 32 procs 1,042,382,561 read/s 442 write/s +90%-10% 64 procs 1,039,644,346 read/s 446 write/s +90%-10% 128 procs 1,049,228,432 read/s 442 write/s +90%-10% 256 procs 1,027,362,194 read/s 477 write/s +90%-10% 512 procs 1,023,097,576 read/s 457 write/s +90%-10% 1024 procs 996,585,722 read/s 436 write/s +90%-10% 2048 procs 948,455,719 read/s 494 write/s +90%-10% 4096 procs 930,094,338 read/s 540 write/s +50%-50% 1 procs 142,015,047 read/s 598 write/s +50%-50% 8 procs 1,066,028,881 read/s 4,300 write/s +50%-50% 16 procs 1,039,210,987 read/s 4,191 write/s +50%-50% 32 procs 1,042,789,993 read/s 4,123 write/s +50%-50% 64 procs 1,040,410,050 read/s 4,102 write/s +50%-50% 128 procs 1,006,464,963 read/s 4,008 write/s +50%-50% 256 procs 1,008,663,071 read/s 4,170 write/s +50%-50% 512 procs 989,864,228 read/s 4,146 write/s +50%-50% 1024 procs 998,826,089 read/s 4,258 write/s +50%-50% 2048 procs 939,110,917 read/s 4,515 write/s +50%-50% 4096 procs 866,137,428 read/s 5,291 write/s +10%-90% 1 procs 135,493,165 read/s 4,968 write/s +10%-90% 8 procs 1,017,928,553 read/s 37,130 write/s +10%-90% 16 procs 1,040,251,193 read/s 37,521 write/s +10%-90% 32 procs 982,115,784 read/s 35,689 write/s +10%-90% 64 procs 975,158,264 read/s 34,041 write/s +10%-90% 128 procs 940,466,888 read/s 34,827 write/s +10%-90% 256 procs 930,871,315 read/s 34,399 write/s +10%-90% 512 procs 892,502,438 read/s 33,955 write/s +10%-90% 1024 procs 834,594,229 read/s 32,953 write/s +10%-90% 2048 procs 785,583,770 read/s 32,882 write/s +10%-90% 4096 procs 688,402,474 read/s 34,646 write/s +``` \ No newline at end of file diff --git a/examples/bench/bench.go b/examples/bench/bench.go new file mode 100644 index 0000000..83275e0 --- /dev/null +++ b/examples/bench/bench.go @@ -0,0 +1,144 @@ +// Copyright (c) Roman Atachiants and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +package main + +import ( + "context" + "encoding/json" + "fmt" + "math/rand" + "os" + "sync" + "sync/atomic" + "time" + + "github.com/dustin/go-humanize" + "github.com/kelindar/async" + "github.com/kelindar/column" +) + +var ( + classes = []string{"fighter", "mage", "rogue"} + races = []string{"human", "elf", "dwarf", "orc"} +) + +func main() { + amount := 1000000 + players := column.NewCollection(column.Options{ + Capacity: amount, + }) + + // insert the data first + createCollection(players, amount) + + // Iterate over various workloads + for _, w := range []int{10, 50, 90} { + + // Iterate over various concurrency levels + for _, n := range []int{1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096} { + + // Create a pool of N goroutines + work := make(chan async.Task, n) + pool := async.Consume(context.Background(), n, work) + + //run(fmt.Sprintf("(%v/%v)-%v", 100-w, w, n), func(b *testing.B) { + var reads int64 + var writes int64 + + var wg sync.WaitGroup + + start := time.Now() + for time.Since(start) < 2*time.Second { + wg.Add(1) + work <- async.NewTask(func(ctx context.Context) (interface{}, error) { + defer wg.Done() + offset := uint32(rand.Int31n(int32(amount - 1))) + + // Given our write probabiliy, randomly update an offset + if rand.Int31n(100) < int32(w) { + players.UpdateAt(offset, "balance", 0.0) + atomic.AddInt64(&writes, 1) + return nil, nil + } + + // Otherwise, randomly read something + players.Query(func(txn *column.Txn) error { + var count int64 + txn.With(races[rand.Int31n(4)]).Range("balance", func(v column.Cursor) { + count++ + }) + atomic.AddInt64(&reads, count) + return nil + }) + + return nil, nil + }) + } + + elapsed := time.Since(start) + readsPerSec := int64(float64(reads) / elapsed.Seconds()) + writesPerSec := int64(float64(writes) / elapsed.Seconds()) + + wg.Wait() + pool.Cancel() + fmt.Printf("%v%%-%v%% %4v procs %20v %15v\n", 100-w, w, n, + humanize.Comma(readsPerSec)+" read/s", + humanize.Comma(writesPerSec)+" write/s", + ) + } + + } +} + +// createCollection loads a collection of players +func createCollection(out *column.Collection, amount int) *column.Collection { + out.CreateColumn("serial", column.ForEnum()) + out.CreateColumn("name", column.ForEnum()) + out.CreateColumn("active", column.ForBool()) + out.CreateColumn("class", column.ForEnum()) + out.CreateColumn("race", column.ForEnum()) + out.CreateColumn("age", column.ForFloat64()) + out.CreateColumn("hp", column.ForFloat64()) + out.CreateColumn("mp", column.ForFloat64()) + out.CreateColumn("balance", column.ForFloat64()) + out.CreateColumn("gender", column.ForEnum()) + out.CreateColumn("guild", column.ForEnum()) + + for _, v := range classes { + class := v + out.CreateIndex(class, "class", func(r column.Reader) bool { + return r.String() == class + }) + } + + for _, v := range races { + race := v + out.CreateIndex(race, "race", func(r column.Reader) bool { + return r.String() == race + }) + } + // Load the 500 rows from JSON + b, err := os.ReadFile("../../fixtures/players.json") + if err != nil { + panic(err) + } + + // Unmarshal the items + var data []map[string]interface{} + if err := json.Unmarshal(b, &data); err != nil { + panic(err) + } + + // Load the data in + for i := 0; i < amount/len(data); i++ { + out.Query(func(txn *column.Txn) error { + for _, p := range data { + txn.Insert(p) + } + return nil + }) + } + + return out +} diff --git a/go.mod b/go.mod index fe1494d..aed26c3 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.0 github.com/kelindar/async v1.0.0 github.com/kelindar/bitmap v1.1.0 github.com/kelindar/smutex v1.0.0 diff --git a/go.sum b/go.sum index a3e9cad..425611c 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/kelindar/async v1.0.0 h1:oJiFAt3fVB/b5zVZKPBU+pP9lR3JVyeox9pYlpdnIK8= github.com/kelindar/async v1.0.0/go.mod h1:bJRlwaRiqdHi+4dpVDNHdwgyRyk6TxpA21fByLf7hIY= github.com/kelindar/bitmap v1.1.0 h1:67PfkHFb+II2HQdeKrPugxMC7fZFEdKq31X+AOaHDq0=