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