├── README.md ├── .gitignore ├── LICENSE └── main.go /README.md: -------------------------------------------------------------------------------- 1 | copy-bench 2 | ========== 3 | 4 | A small benchmarking program to test performance and cache trashing of Tx.Copy(). 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 BoltDB 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "runtime" 12 | "time" 13 | 14 | "github.com/boltdb/bolt" 15 | ) 16 | 17 | const batchSize = 10000 18 | const itemCount = 4000000 19 | const keySize = 8 20 | const valueSize = 1024 21 | const iteratePct = 0.2 22 | 23 | func main() { 24 | log.SetFlags(0) 25 | flag.Parse() 26 | path := flag.Arg(0) 27 | if path == "" { 28 | log.Fatal("usage: copy-bench PATH") 29 | } 30 | 31 | log.SetFlags(log.LstdFlags | log.Lmicroseconds) 32 | runtime.GOMAXPROCS(2) 33 | 34 | // Check if this is a new data file. 35 | _, err := os.Stat(path) 36 | isNew := os.IsNotExist(err) 37 | 38 | // Open database. 39 | db, err := bolt.Open(path, 0600) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | // Populate the initial database. 45 | if isNew { 46 | if err := seed(db); err != nil { 47 | log.Fatal(err) 48 | } 49 | } 50 | 51 | // Print stats of the db. 52 | if err := stat(db); err != nil { 53 | log.Fatal(err) 54 | } 55 | 56 | // Iterate once to push pages into memory. 57 | c := make(chan bool) 58 | fmt.Println("first run (ignore)") 59 | go func() { iterate(db, c) }() 60 | c <- true 61 | fmt.Println("") 62 | 63 | // Time iteration without copy. 64 | fmt.Println("iterate only") 65 | go iterate(db, c) 66 | time.Sleep(2 * time.Second) 67 | c <- true 68 | fmt.Println("") 69 | 70 | // Start iterator thread. 71 | fmt.Println("iterate during copy") 72 | go iterate(db, c) 73 | 74 | // Begin copy of the database. 75 | if err := dbcopy(db); err != nil { 76 | log.Fatal(err) 77 | } 78 | 79 | // Notify iterator of db copy completion. 80 | c <- true 81 | time.Sleep(100 * time.Millisecond) 82 | } 83 | 84 | // seed inserts an initial dataset into the database. 85 | func seed(db *bolt.DB) error { 86 | log.Print("seeding") 87 | 88 | var count int 89 | var size int64 90 | for i := 0; i < itemCount; i += batchSize { 91 | err := db.Update(func(tx *bolt.Tx) error { 92 | b, err := tx.CreateBucketIfNotExists([]byte("root")) 93 | if err != nil { 94 | return fmt.Errorf("create bucket: %s", err) 95 | } 96 | 97 | for j := 0; j < batchSize; j++ { 98 | k, v := make([]byte, keySize), make([]byte, valueSize) 99 | binary.BigEndian.PutUint64(k, uint64(count)) 100 | if err := b.Put(k, v); err != nil { 101 | return fmt.Errorf("put: ", err) 102 | } 103 | count++ 104 | } 105 | 106 | size = tx.Size() 107 | 108 | return nil 109 | }) 110 | if err != nil { 111 | return err 112 | } 113 | log.Printf(" %d rows, %d bytes", count, size) 114 | } 115 | log.Print("(done)") 116 | fmt.Println("") 117 | 118 | if count != itemCount { 119 | return fmt.Errorf("invalid insert count: %d != %d", count, itemCount) 120 | } 121 | 122 | return nil 123 | } 124 | 125 | // stat prints out stats about the db. 126 | func stat(db *bolt.DB) error { 127 | return db.View(func(tx *bolt.Tx) error { 128 | fmt.Printf("size: %d bytes\n", tx.Size()) 129 | fmt.Println("") 130 | return nil 131 | }) 132 | } 133 | 134 | // iterate continually loops over a subsection of the database and reads key/values. 135 | func iterate(db *bolt.DB, c chan bool) { 136 | max := make([]byte, keySize) 137 | binary.BigEndian.PutUint64(max, uint64(itemCount*iteratePct)) 138 | 139 | var d time.Duration 140 | var n int 141 | loop: 142 | for { 143 | t := time.Now() 144 | 145 | // Loop over a subset of the data. 146 | var count int 147 | db.View(func(tx *bolt.Tx) error { 148 | c := tx.Bucket([]byte("root")).Cursor() 149 | for k, _ := c.First(); k != nil && bytes.Compare(k, max) == -1; k, _ = c.Next() { 150 | count++ 151 | } 152 | return nil 153 | }) 154 | log.Printf(" iterate: %v (n=%d)", time.Since(t), count) 155 | d += time.Since(t) 156 | n++ 157 | 158 | // Check for completion. 159 | select { 160 | case <-c: 161 | break loop 162 | default: 163 | } 164 | } 165 | 166 | fmt.Printf("iterate: avg: %v (n=%d)\n", (d / time.Duration(n)), n) 167 | } 168 | 169 | // dbcopy performs a copy of the database file. 170 | func dbcopy(db *bolt.DB) error { 171 | t := time.Now() 172 | err := db.View(func(tx *bolt.Tx) error { 173 | return tx.Copy(ioutil.Discard) 174 | }) 175 | if err != nil { 176 | return err 177 | } 178 | fmt.Printf("copy: %v\n", time.Since(t)) 179 | 180 | return nil 181 | } 182 | --------------------------------------------------------------------------------