github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/iavl/benchmarks/bench_test.go (about) 1 package benchmarks 2 3 import ( 4 "fmt" 5 "math/rand" 6 "os" 7 "runtime" 8 "testing" 9 10 "github.com/stretchr/testify/require" 11 12 "github.com/gnolang/gno/tm2/pkg/db" 13 _ "github.com/gnolang/gno/tm2/pkg/db/_all" 14 "github.com/gnolang/gno/tm2/pkg/iavl" 15 ) 16 17 const historySize = 20 18 19 func randBytes(length int) []byte { 20 key := make([]byte, length) 21 // math.rand.Read always returns err=nil 22 // we do not need cryptographic randomness for this test: 23 rand.Read(key) 24 return key 25 } 26 27 func prepareTree(b *testing.B, db db.DB, size, keyLen, dataLen int) (*iavl.MutableTree, [][]byte) { 28 b.Helper() 29 30 t := iavl.NewMutableTree(db, size) 31 keys := make([][]byte, size) 32 33 for i := 0; i < size; i++ { 34 key := randBytes(keyLen) 35 t.Set(key, randBytes(dataLen)) 36 keys[i] = key 37 } 38 commitTree(b, t) 39 runtime.GC() 40 return t, keys 41 } 42 43 // commit tree saves a new version and deletes and old one... 44 func commitTree(b *testing.B, t *iavl.MutableTree) { 45 b.Helper() 46 47 t.Hash() 48 _, version, err := t.SaveVersion() 49 if err != nil { 50 b.Errorf("Can't save: %v", err) 51 } 52 if version > historySize { 53 err = t.DeleteVersion(version - historySize) 54 if err != nil { 55 b.Errorf("Can't delete: %v", err) 56 } 57 } 58 } 59 60 func runQueries(b *testing.B, t *iavl.MutableTree, keyLen int) { 61 b.Helper() 62 63 for i := 0; i < b.N; i++ { 64 q := randBytes(keyLen) 65 t.Get(q) 66 } 67 } 68 69 func runKnownQueries(b *testing.B, t *iavl.MutableTree, keys [][]byte) { 70 b.Helper() 71 72 l := int32(len(keys)) 73 for i := 0; i < b.N; i++ { 74 q := keys[rand.Int31n(l)] 75 t.Get(q) 76 } 77 } 78 79 func runUpdate(b *testing.B, t *iavl.MutableTree, dataLen, blockSize int, keys [][]byte) *iavl.MutableTree { 80 b.Helper() 81 82 l := int32(len(keys)) 83 for i := 1; i <= b.N; i++ { 84 key := keys[rand.Int31n(l)] 85 t.Set(key, randBytes(dataLen)) 86 if i%blockSize == 0 { 87 commitTree(b, t) 88 } 89 } 90 return t 91 } 92 93 // runBlock measures time for an entire block, not just one tx 94 func runBlock(b *testing.B, t *iavl.MutableTree, keyLen, dataLen, blockSize int, keys [][]byte) *iavl.MutableTree { 95 b.Helper() 96 97 l := int32(len(keys)) 98 99 // XXX: This was adapted to work with VersionedTree but needs to be re-thought. 100 101 lastCommit := t 102 realTree := t 103 // check := t 104 105 for i := 0; i < b.N; i++ { 106 for j := 0; j < blockSize; j++ { 107 // 50% insert, 50% update 108 var key []byte 109 if i%2 == 0 { 110 key = keys[rand.Int31n(l)] 111 } else { 112 key = randBytes(keyLen) 113 } 114 data := randBytes(dataLen) 115 116 // perform query and write on check and then real 117 // check.Get(key) 118 // check.Set(key, data) 119 realTree.Get(key) 120 realTree.Set(key, data) 121 } 122 123 // at the end of a block, move it all along.... 124 commitTree(b, realTree) 125 lastCommit = realTree 126 } 127 128 return lastCommit 129 } 130 131 func BenchmarkRandomBytes(b *testing.B) { 132 benchmarks := []struct { 133 length int 134 }{ 135 {4}, {16}, {32}, {100}, {1000}, 136 } 137 for _, bench := range benchmarks { 138 name := fmt.Sprintf("random-%d", bench.length) 139 b.Run(name, func(b *testing.B) { 140 for i := 0; i < b.N; i++ { 141 randBytes(bench.length) 142 } 143 runtime.GC() 144 }) 145 } 146 } 147 148 type benchmark struct { 149 dbType db.BackendType 150 initSize, blockSize int 151 keyLen, dataLen int 152 } 153 154 func BenchmarkSmall(b *testing.B) { 155 ls := db.BackendList() 156 bs := make([]benchmark, 0, len(ls)) 157 for _, backend := range ls { 158 bs = append(bs, benchmark{backend, 1_000, 100, 16, 40}) 159 } 160 runBenchmarks(b, bs) 161 } 162 163 func BenchmarkMedium(b *testing.B) { 164 ls := db.BackendList() 165 bs := make([]benchmark, 0, len(ls)) 166 for _, backend := range ls { 167 bs = append(bs, benchmark{backend, 100_000, 100, 16, 40}) 168 } 169 runBenchmarks(b, bs) 170 } 171 172 func BenchmarkLarge(b *testing.B) { 173 ls := db.BackendList() 174 bs := make([]benchmark, 0, len(ls)) 175 for _, backend := range ls { 176 bs = append(bs, benchmark{backend, 1_000_000, 100, 16, 40}) 177 } 178 runBenchmarks(b, bs) 179 } 180 181 func BenchmarkLevelDBBatchSizes(b *testing.B) { 182 benchmarks := []benchmark{ 183 {db.GoLevelDBBackend, 100000, 5, 16, 40}, 184 {db.GoLevelDBBackend, 100000, 25, 16, 40}, 185 {db.GoLevelDBBackend, 100000, 100, 16, 40}, 186 {db.GoLevelDBBackend, 100000, 400, 16, 40}, 187 {db.GoLevelDBBackend, 100000, 2000, 16, 40}, 188 } 189 runBenchmarks(b, benchmarks) 190 } 191 192 // BenchmarkLevelDBLargeData is intended to push disk limits 193 // in the leveldb, to make sure not everything is cached 194 func BenchmarkLevelDBLargeData(b *testing.B) { 195 benchmarks := []benchmark{ 196 {db.GoLevelDBBackend, 50000, 100, 32, 100}, 197 {db.GoLevelDBBackend, 50000, 100, 32, 1000}, 198 {db.GoLevelDBBackend, 50000, 100, 32, 10000}, 199 {db.GoLevelDBBackend, 50000, 100, 32, 100000}, 200 } 201 runBenchmarks(b, benchmarks) 202 } 203 204 func runBenchmarks(b *testing.B, benchmarks []benchmark) { 205 b.Helper() 206 207 for _, bb := range benchmarks { 208 prefix := fmt.Sprintf("%s-%d-%d-%d-%d", bb.dbType, bb.initSize, 209 bb.blockSize, bb.keyLen, bb.dataLen) 210 211 // prepare a dir for the db and cleanup afterwards 212 dirName := fmt.Sprintf("./%s-db", prefix) 213 defer func() { 214 err := os.RemoveAll(dirName) 215 if err != nil { 216 b.Errorf("%+v\n", err) 217 } 218 }() 219 220 // note that "" leads to nil backing db! 221 var d db.DB 222 var err error 223 if bb.dbType != "nodb" { 224 d, err = db.NewDB("test", bb.dbType, dirName) 225 require.NoError(b, err) 226 defer d.Close() 227 } 228 b.Run(prefix, func(sub *testing.B) { 229 runSuite(sub, d, bb.initSize, bb.blockSize, bb.keyLen, bb.dataLen) 230 }) 231 } 232 } 233 234 // returns number of MB in use 235 func memUseMB() float64 { 236 var mem runtime.MemStats 237 runtime.ReadMemStats(&mem) 238 asize := mem.Alloc 239 mb := float64(asize) / 1000000 240 return mb 241 } 242 243 func runSuite(b *testing.B, d db.DB, initSize, blockSize, keyLen, dataLen int) { 244 b.Helper() 245 246 // measure mem usage 247 runtime.GC() 248 init := memUseMB() 249 250 t, keys := prepareTree(b, d, initSize, keyLen, dataLen) 251 used := memUseMB() - init 252 fmt.Printf("Init Tree took %0.2f MB\n", used) 253 254 b.ResetTimer() 255 256 b.Run("query-miss", func(sub *testing.B) { 257 runQueries(sub, t, keyLen) 258 }) 259 b.Run("query-hits", func(sub *testing.B) { 260 runKnownQueries(sub, t, keys) 261 }) 262 b.Run("update", func(sub *testing.B) { 263 t = runUpdate(sub, t, dataLen, blockSize, keys) 264 }) 265 b.Run("block", func(sub *testing.B) { 266 t = runBlock(sub, t, keyLen, dataLen, blockSize, keys) 267 }) 268 269 // both of these edit size of the tree too much 270 // need to run with their own tree 271 // t = nil // for gc 272 // b.Run("insert", func(sub *testing.B) { 273 // it, _ := prepareTree(d, initSize, keyLen, dataLen) 274 // sub.ResetTimer() 275 // runInsert(sub, it, keyLen, dataLen, blockSize) 276 // }) 277 // b.Run("delete", func(sub *testing.B) { 278 // dt, dkeys := prepareTree(d, initSize+sub.N, keyLen, dataLen) 279 // sub.ResetTimer() 280 // runDelete(sub, dt, blockSize, dkeys) 281 // }) 282 }