github.com/klaytn/klaytn@v1.12.1/storage/database/leveldb_bench_common_test.go (about) 1 // Copyright 2019 The klaytn Authors 2 // This file is part of the klaytn library. 3 // 4 // The klaytn library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The klaytn library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>. 16 17 package database 18 19 import ( 20 "errors" 21 "fmt" 22 "os" 23 "testing" 24 25 "github.com/klaytn/klaytn/common" 26 "github.com/stretchr/testify/assert" 27 ) 28 29 const ( 30 // numDataInsertions is the amount of data to be pre-stored in the DB before it is read 31 numDataInsertions = 1000 * 1000 * 2 32 // readArea is the range of data to be attempted to read from the data stored in the db. 33 readArea = numDataInsertions / 512 34 // To read the data cached in the DB, the most recently input data of the DB should be read. 35 // This is the offset to specify the starting point of the data to read. 36 readCacheOffset = numDataInsertions - readArea 37 // primeNumber is used as an interval for reading data. 38 // It is a number used to keep the data from being read consecutively. 39 // This interval is used because the prime number is small relative to any number except for its multiple, 40 // so you can access the whole range of data by cycling the data by a small number of intervals. 41 primeNumber = 71887 42 // levelDBMemDBSize is the size of internal memory database of LevelDB. Data is first saved to memDB and then moved to persistent storage. 43 levelDBMemDBSize = 64 44 // numOfGet is the number read per goroutine for the parallel read test. 45 numberOfGet = 10000 46 ) 47 48 // initTestDB creates the db and inputs the data in db for valSize 49 func initTestDB(valueSize int) (string, Database, [][]byte, error) { 50 dir, err := os.MkdirTemp("", "bench-DB") 51 if err != nil { 52 return "", nil, nil, errors.New(fmt.Sprintf("can't create temporary directory: %v", err)) 53 } 54 dbc := &DBConfig{Dir: dir, DBType: LevelDB, LevelDBCacheSize: levelDBMemDBSize, OpenFilesLimit: 0} 55 db, err := newDatabase(dbc, 0) 56 if err != nil { 57 return "", nil, nil, errors.New(fmt.Sprintf("can't create database: %v", err)) 58 } 59 keys, values := genKeysAndValues(valueSize, numDataInsertions) 60 61 for i, key := range keys { 62 if err := db.Put(key, values[i]); err != nil { 63 return "", nil, nil, errors.New(fmt.Sprintf("fail to put data to db: %v", err)) 64 } 65 } 66 67 return dir, db, keys, nil 68 } 69 70 // benchmarkReadDBFromFile is a benchmark function that reads the data stored in the ldb file. 71 // Reads the initially entered data to read the value stored in the file. 72 func benchmarkReadDBFromFile(b *testing.B, valueSize int) { 73 dir, db, keys, err := initTestDB(valueSize) 74 defer os.RemoveAll(dir) 75 defer db.Close() 76 if err != nil { 77 b.Fatalf("database initialization error: %v", err) 78 } 79 80 b.ResetTimer() 81 for i := 0; i < b.N; i++ { 82 db.Get(keys[(i*primeNumber)%readArea]) 83 } 84 } 85 86 // benchmarkReadDBFromMemDB is a benchmark function that reads data stored in memDB. 87 // Read the data entered later to read the value stored in memDB, not in the disk storage. 88 func benchmarkReadDBFromMemDB(b *testing.B, valueSize int) { 89 dir, db, keys, err := initTestDB(valueSize) 90 defer os.RemoveAll(dir) 91 defer db.Close() 92 if err != nil { 93 b.Fatalf("database initialization error: %v", err) 94 } 95 96 b.ResetTimer() 97 for i := 0; i < b.N; i++ { 98 db.Get(keys[(readCacheOffset)+(i*primeNumber)%readArea]) 99 } 100 } 101 102 // getReadDataOptions is a test case for measuring read performance. 103 var getReadDataOptions = [...]struct { 104 name string 105 valueLength int 106 testFunc func(b *testing.B, valueSize int) 107 }{ 108 {"DBFromFile1", 1, benchmarkReadDBFromFile}, 109 {"DBFromFile128", 128, benchmarkReadDBFromFile}, 110 {"DBFromFile256", 256, benchmarkReadDBFromFile}, 111 {"DBFromFile512", 512, benchmarkReadDBFromFile}, 112 113 {"DBFromMem1", 1, benchmarkReadDBFromMemDB}, 114 {"DBFromMem128", 128, benchmarkReadDBFromMemDB}, 115 {"DBFromMem256", 256, benchmarkReadDBFromMemDB}, 116 {"DBFromMem512", 512, benchmarkReadDBFromMemDB}, 117 118 {"LRUCacheSingle1", 1, benchmarkLruCacheGetSingle}, 119 {"LRUCacheSingle128", 128, benchmarkLruCacheGetSingle}, 120 {"LRUCacheSingle256", 256, benchmarkLruCacheGetSingle}, 121 {"LRUCacheSingle512", 512, benchmarkLruCacheGetSingle}, 122 123 {"FIFOCacheSingle1", 1, benchmarkFIFOCacheGetSingle}, 124 {"FIFOCacheSingle128", 128, benchmarkFIFOCacheGetSingle}, 125 {"FIFOCacheSingle256", 256, benchmarkFIFOCacheGetSingle}, 126 {"FIFOCacheSingle512", 512, benchmarkFIFOCacheGetSingle}, 127 128 {"LRUCacheParallel1", 1, benchmarkLruCacheCacheGetParallel}, 129 {"LRUCacheParallel128", 128, benchmarkLruCacheCacheGetParallel}, 130 {"LRUCacheParallel256", 256, benchmarkLruCacheCacheGetParallel}, 131 {"LRUCacheParallel512", 512, benchmarkLruCacheCacheGetParallel}, 132 133 {"FIFOCacheParallel1", 1, benchmarkFIFOCacheGetParallel}, 134 {"FIFOCacheParallel128", 128, benchmarkFIFOCacheGetParallel}, 135 {"FIFOCacheParallel256", 256, benchmarkFIFOCacheGetParallel}, 136 {"FIFOCacheParallel512", 512, benchmarkFIFOCacheGetParallel}, 137 } 138 139 // Benchmark_read_data is a benchmark that measures data read performance in DB and cache. 140 func Benchmark_read_data(b *testing.B) { 141 for _, bm := range getReadDataOptions { 142 b.Run(bm.name, func(b *testing.B) { 143 bm.testFunc(b, bm.valueLength) 144 }) 145 } 146 } 147 148 // benchmarkFIFOCacheGetParallel measures the performance of the fifoCache when reading data in parallel 149 func benchmarkFIFOCacheGetParallel(b *testing.B, valueSize int) { 150 cache := common.NewCache(common.FIFOCacheConfig{CacheSize: numDataInsertions}) 151 benchmarkCacheGetParallel(b, cache, valueSize) 152 } 153 154 // benchmarkLruCacheCacheGetParallel measures the performance of the lruCache when reading data in parallel 155 func benchmarkLruCacheCacheGetParallel(b *testing.B, valueSize int) { 156 cache := common.NewCache(common.LRUConfig{CacheSize: numDataInsertions}) 157 benchmarkCacheGetParallel(b, cache, valueSize) 158 } 159 160 // benchmarkCacheGetParallel is a benchmark for measuring performance 161 // when cacheSize data is entered into the cache and then read in parallel. 162 func benchmarkCacheGetParallel(b *testing.B, cache common.Cache, valueSize int) { 163 hashKeys := initCacheData(cache, valueSize) 164 b.ResetTimer() 165 b.RunParallel(func(pb *testing.PB) { 166 for pb.Next() { 167 for i := 0; i < numberOfGet; i++ { 168 key := hashKeys[(i*primeNumber)%numDataInsertions] 169 cache.Get(key) 170 } 171 } 172 }) 173 } 174 175 // benchmarkFIFOCacheGetSingle is a benchmark to read fifoCache serially. 176 func benchmarkFIFOCacheGetSingle(b *testing.B, valueSize int) { 177 cache := common.NewCache(common.FIFOCacheConfig{CacheSize: numDataInsertions}) 178 benchmarkCacheGetSingle(b, cache, valueSize) 179 } 180 181 // benchmarkLruCacheGetSingle is a benchmark to read lruCache serially. 182 func benchmarkLruCacheGetSingle(b *testing.B, valueSize int) { 183 cache := common.NewCache(common.LRUConfig{CacheSize: numDataInsertions}) 184 benchmarkCacheGetSingle(b, cache, valueSize) 185 } 186 187 // benchmarkCacheGetSingle is a benchmark for measuring performance 188 // when cacheSize data is entered into the cache and then serially read. 189 func benchmarkCacheGetSingle(b *testing.B, cache common.Cache, valueSize int) { 190 hashKeys := initCacheData(cache, valueSize) 191 b.ResetTimer() 192 193 for i := 0; i < b.N; i++ { 194 key := hashKeys[(i*primeNumber)%numDataInsertions] 195 cache.Get(key) 196 } 197 } 198 199 // initCacheData initializes the cache by entering test data into the cache. 200 func initCacheData(cache common.Cache, valueSize int) []common.Hash { 201 keys, values := genKeysAndValues(valueSize, numDataInsertions) 202 hashKeys := make([]common.Hash, 0, numDataInsertions) 203 204 for i, key := range keys { 205 var hashKey common.Hash 206 copy(hashKey[:], key[:32]) 207 hashKeys = append(hashKeys, hashKey) 208 cache.Add(hashKey, values[i]) 209 } 210 211 return hashKeys 212 } 213 214 // TestLRUShardCacheAddressKey is a test to make sure add and get commands work when using Address as key. 215 // Cache hit for all data. 216 func TestLRUShardCacheAddressKey(t *testing.T) { 217 cache := common.NewCache(common.LRUShardConfig{CacheSize: 40960, NumShards: 4096}) 218 219 for i := 0; i < 4096; i++ { 220 cache.Add(getAddressKey(i), i) 221 } 222 223 for i := 0; i < 4096; i++ { 224 data, ok := cache.Get(getAddressKey(i)) 225 assert.True(t, ok) 226 assert.Equal(t, i, data.(int)) 227 } 228 } 229 230 // TestLRUShardCacheHashKey is a test to check whether the add and get commands are working 231 // when the Address created by SetBytesFromFront is used as key. Cache hit for all data. 232 func TestLRUShardCacheAddressKeyFromFront(t *testing.T) { 233 cache := common.NewCache(common.LRUShardConfig{CacheSize: 40960, NumShards: 4096}) 234 235 for i := 0; i < 4096; i++ { 236 cache.Add(getAddressKeyFromFront(i), i) 237 } 238 239 for i := 0; i < 4096; i++ { 240 data, ok := cache.Get(getAddressKeyFromFront(i)) 241 assert.True(t, ok) 242 assert.Equal(t, i, data.(int)) 243 } 244 } 245 246 // TestLRUShardCacheHashKey is a test to see if add and get commands work when using Hash as key. 247 // Cache hit for all data. 248 func TestLRUShardCacheHashKey(t *testing.T) { 249 cache := common.NewCache(common.LRUShardConfig{CacheSize: 40960, NumShards: 4096}) 250 251 for i := 0; i < 4096; i++ { 252 cache.Add(getHashKey(i), i) 253 } 254 255 for i := 0; i < 4096; i++ { 256 data, ok := cache.Get(getHashKey(i)) 257 assert.True(t, ok) 258 assert.Equal(t, i, data.(int)) 259 } 260 } 261 262 // getHashKey returns an int converted to a Hash. 263 func getHashKey(i int) common.Hash { 264 var byteArray interface{} 265 if i>>8 == 0 { 266 byteArray = []byte{byte(i)} 267 } else { 268 byteArray = []byte{byte(i >> 8), byte(i)} 269 } 270 return common.BytesToHash(byteArray.([]byte)) 271 } 272 273 // getAddressKey returns an int converted to a Address. 274 func getAddressKey(i int) common.Address { 275 var byteArray interface{} 276 if i>>8 == 0 { 277 byteArray = []byte{byte(i)} 278 } else { 279 byteArray = []byte{byte(i >> 8), byte(i)} 280 } 281 return common.BytesToAddress(byteArray.([]byte)) 282 } 283 284 // getAddressKeyFromFront converts an int to an Address and returns it from 0 in the array. 285 func getAddressKeyFromFront(i int) common.Address { 286 var addr common.Address 287 var byteArray interface{} 288 if i>>8 == 0 { 289 byteArray = []byte{byte(i)} 290 } else { 291 byteArray = []byte{byte(i), byte(i >> 8)} 292 } 293 addr.SetBytesFromFront(byteArray.([]byte)) 294 return addr 295 }