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  }