From 7ae6171374a04d62e320ccd40e238cffe651cfb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 12 Dec 2024 21:34:02 +0200 Subject: [PATCH] Additional tests, benchmarks. --- .../txcachemocks/selectionSessionMock.go | 4 + txcache/eviction_test.go | 8 +- txcache/selection.go | 2 + txcache/selectionSessionWrapper.go | 3 +- txcache/selectionSessionWrapper_test.go | 103 ++++++++++++++++++ txcache/selection_test.go | 16 +-- txcache/testutils_test.go | 33 ++++++ txcache/txCache_test.go | 16 +-- 8 files changed, 161 insertions(+), 24 deletions(-) diff --git a/testscommon/txcachemocks/selectionSessionMock.go b/testscommon/txcachemocks/selectionSessionMock.go index db789b51..e6edd8ca 100644 --- a/testscommon/txcachemocks/selectionSessionMock.go +++ b/testscommon/txcachemocks/selectionSessionMock.go @@ -12,6 +12,8 @@ import ( type SelectionSessionMock struct { mutex sync.Mutex + NumCallsGetAccountState int + AccountStateByAddress map[string]*types.AccountState GetAccountStateCalled func(address []byte) (*types.AccountState, error) IsIncorrectlyGuardedCalled func(tx data.TransactionHandler) bool @@ -57,6 +59,8 @@ func (mock *SelectionSessionMock) GetAccountState(address []byte) (*types.Accoun mock.mutex.Lock() defer mock.mutex.Unlock() + mock.NumCallsGetAccountState++ + if mock.GetAccountStateCalled != nil { return mock.GetAccountStateCalled(address) } diff --git a/txcache/eviction_test.go b/txcache/eviction_test.go index 9b435d8e..cbb911c0 100644 --- a/txcache/eviction_test.go +++ b/txcache/eviction_test.go @@ -220,8 +220,8 @@ func TestBenchmarkTxCache_DoEviction(t *testing.T) { // Thread(s) per core: 2 // Core(s) per socket: 4 // - // 0.119274s (TestBenchmarkTxCache_DoEviction/numSenders_=_35000,_numTransactions_=_10) - // 0.484147s (TestBenchmarkTxCache_DoEviction/numSenders_=_100000,_numTransactions_=_5) - // 0.504588s (TestBenchmarkTxCache_DoEviction/numSenders_=_10000,_numTransactions_=_100) - // 0.571885s (TestBenchmarkTxCache_DoEviction/numSenders_=_400000,_numTransactions_=_1) + // 0.092625s (TestBenchmarkTxCache_DoEviction/numSenders_=_35000,_numTransactions_=_10) + // 0.426718s (TestBenchmarkTxCache_DoEviction/numSenders_=_100000,_numTransactions_=_5) + // 0.546757s (TestBenchmarkTxCache_DoEviction/numSenders_=_10000,_numTransactions_=_100) + // 0.542678s (TestBenchmarkTxCache_DoEviction/numSenders_=_400000,_numTransactions_=_1) } diff --git a/txcache/selection.go b/txcache/selection.go index c2c4b072..10e81bff 100644 --- a/txcache/selection.go +++ b/txcache/selection.go @@ -90,6 +90,8 @@ func selectTransactionsFromBunches(session SelectionSession, bunches []bunchOfTr return selectedTransactions, accumulatedGas } +// Note (future micro-optimization): we can merge "detectSkippableSender()" and "detectSkippableTransaction()" into a single function, +// any share the result of "sessionWrapper.getNonce()". func detectSkippableSender(sessionWrapper *selectionSessionWrapper, item *transactionsHeapItem) bool { nonce := sessionWrapper.getNonce(item.sender) diff --git a/txcache/selectionSessionWrapper.go b/txcache/selectionSessionWrapper.go index 86358c93..525146af 100644 --- a/txcache/selectionSessionWrapper.go +++ b/txcache/selectionSessionWrapper.go @@ -53,8 +53,7 @@ func (sessionWrapper *selectionSessionWrapper) getAccountRecord(address []byte) } func (sessionWrapper *selectionSessionWrapper) getNonce(address []byte) uint64 { - record := sessionWrapper.getAccountRecord(address) - return record.initialNonce + return sessionWrapper.getAccountRecord(address).initialNonce } func (sessionWrapper *selectionSessionWrapper) accumulateConsumedBalance(tx *WrappedTransaction) { diff --git a/txcache/selectionSessionWrapper_test.go b/txcache/selectionSessionWrapper_test.go index 24aa9775..7a741cdb 100644 --- a/txcache/selectionSessionWrapper_test.go +++ b/txcache/selectionSessionWrapper_test.go @@ -1,9 +1,11 @@ package txcache import ( + "fmt" "math/big" "testing" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-storage-go/testscommon/txcachemocks" "github.com/stretchr/testify/require" ) @@ -131,3 +133,104 @@ func TestSelectionSessionWrapper_detectWillFeeExceedBalance(t *testing.T) { }) } +func TestBenchmarkSelectionSessionWrapper_getNonce(t *testing.T) { + sw := core.NewStopWatch() + + t.Run("getNonce: numAccounts = 300, numTransactionsPerAccount = 100", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + sessionWrapper := newSelectionSessionWrapper(session) + + numAccounts := 300 + numTransactions := 100 + // See "detectSkippableSender()" and "detectSkippableTransaction()". + numCallsGetNoncePerTransaction := 2 + numCallsGetNoncePerAccount := numTransactions * numCallsGetNoncePerTransaction + + for i := 0; i < numAccounts; i++ { + session.SetNonce(randomAddresses.getItem(i), uint64(i)) + } + + sw.Start(t.Name()) + + for i := 0; i < numAccounts; i++ { + for j := 0; j < numCallsGetNoncePerAccount; j++ { + _ = sessionWrapper.getNonce(randomAddresses.getItem(i)) + } + } + + sw.Stop(t.Name()) + + require.Equal(t, numAccounts, session.NumCallsGetAccountState) + }) + + t.Run("getNonce: numAccounts = 10_000, numTransactionsPerAccount = 3", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + sessionWrapper := newSelectionSessionWrapper(session) + + numAccounts := 10_000 + numTransactions := 3 + // See "detectSkippableSender()" and "detectSkippableTransaction()". + numCallsGetNoncePerTransaction := 2 + numCallsGetNoncePerAccount := numTransactions * numCallsGetNoncePerTransaction + + for i := 0; i < numAccounts; i++ { + session.SetNonce(randomAddresses.getItem(i), uint64(i)) + } + + sw.Start(t.Name()) + + for i := 0; i < numAccounts; i++ { + for j := 0; j < numCallsGetNoncePerAccount; j++ { + _ = sessionWrapper.getNonce(randomAddresses.getItem(i)) + } + } + + sw.Stop(t.Name()) + + require.Equal(t, numAccounts, session.NumCallsGetAccountState) + }) + + t.Run("getNonce: numAccounts = 30_000, numTransactionsPerAccount = 1", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + sessionWrapper := newSelectionSessionWrapper(session) + + numAccounts := 30_000 + numTransactions := 1 + // See "detectSkippableSender()" and "detectSkippableTransaction()". + numCallsGetNoncePerTransaction := 2 + numCallsGetNoncePerAccount := numTransactions * numCallsGetNoncePerTransaction + + for i := 0; i < numAccounts; i++ { + session.SetNonce(randomAddresses.getItem(i), uint64(i)) + } + + sw.Start(t.Name()) + + for i := 0; i < numAccounts; i++ { + for j := 0; j < numCallsGetNoncePerAccount; j++ { + _ = sessionWrapper.getNonce(randomAddresses.getItem(i)) + } + } + + sw.Stop(t.Name()) + + require.Equal(t, numAccounts, session.NumCallsGetAccountState) + }) + + for name, measurement := range sw.GetMeasurementsMap() { + fmt.Printf("%fs (%s)\n", measurement, name) + } + + // (1) + // Vendor ID: GenuineIntel + // Model name: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz + // CPU family: 6 + // Model: 140 + // Thread(s) per core: 2 + // Core(s) per socket: 4 + // + // Session wrapper operations should have a negligible impact on the performance! + // 0.000826s (TestBenchmarkSelectionSessionWrapper_queriesBreakdown/getNonce:_numAccounts_=_300,_numTransactionsPerAccount_=_100) + // 0.003263s (TestBenchmarkSelectionSessionWrapper_queriesBreakdown/getNonce:_numAccounts_=_10_000,_numTransactionsPerAccount_=_3) + // 0.010291s (TestBenchmarkSelectionSessionWrapper_queriesBreakdown/getNonce:_numAccounts_=_30_000,_numTransactionsPerAccount_=_1) +} diff --git a/txcache/selection_test.go b/txcache/selection_test.go index 798876cd..18a052a7 100644 --- a/txcache/selection_test.go +++ b/txcache/selection_test.go @@ -462,10 +462,10 @@ func TestBenchmarkTxCache_selectTransactionsFromBunches(t *testing.T) { // Thread(s) per core: 2 // Core(s) per socket: 4 // - // 0.057519s (TestBenchmarkTxCache_selectTransactionsFromBunches/numSenders_=_1000,_numTransactions_=_1000) - // 0.048023s (TestBenchmarkTxCache_selectTransactionsFromBunches/numSenders_=_10000,_numTransactions_=_100) - // 0.289515s (TestBenchmarkTxCache_selectTransactionsFromBunches/numSenders_=_100000,_numTransactions_=_3) - // 0.460242s (TestBenchmarkTxCache_selectTransactionsFromBunches/numSenders_=_300000,_numTransactions_=_1) + // 0.074999s (TestBenchmarkTxCache_selectTransactionsFromBunches/numSenders_=_1000,_numTransactions_=_1000) + // 0.059256s (TestBenchmarkTxCache_selectTransactionsFromBunches/numSenders_=_10000,_numTransactions_=_100) + // 0.389317s (TestBenchmarkTxCache_selectTransactionsFromBunches/numSenders_=_100000,_numTransactions_=_3) + // 0.498457s (TestBenchmarkTxCache_selectTransactionsFromBunches/numSenders_=_300000,_numTransactions_=_1) } func TestTxCache_selectTransactionsFromBunches_loopBreaks_whenTakesTooLong(t *testing.T) { @@ -572,8 +572,8 @@ func TestBenchmarkTxCache_doSelectTransactions(t *testing.T) { // Thread(s) per core: 2 // Core(s) per socket: 4 // - // 0.042209s (TestBenchmarkTxCache_doSelectTransactions/numSenders_=_10000,_numTransactions_=_100,_maxNum_=_30_000) - // 0.055784s (TestBenchmarkTxCache_doSelectTransactions/numSenders_=_50000,_numTransactions_=_2,_maxNum_=_30_000) - // 0.078637s (TestBenchmarkTxCache_doSelectTransactions/numSenders_=_100000,_numTransactions_=_1,_maxNum_=_30_000) - // 0.222669s (TestBenchmarkTxCache_doSelectTransactions/numSenders_=_300000,_numTransactions_=_1,_maxNum_=_30_000) + // 0.048709s (TestBenchmarkTxCache_doSelectTransactions/numSenders_=_10000,_numTransactions_=_100,_maxNum_=_30_000) + // 0.076177s (TestBenchmarkTxCache_doSelectTransactions/numSenders_=_50000,_numTransactions_=_2,_maxNum_=_30_000) + // 0.104399s (TestBenchmarkTxCache_doSelectTransactions/numSenders_=_100000,_numTransactions_=_1,_maxNum_=_30_000) + // 0.319060s (TestBenchmarkTxCache_doSelectTransactions/numSenders_=_300000,_numTransactions_=_1,_maxNum_=_30_000) } diff --git a/txcache/testutils_test.go b/txcache/testutils_test.go index 388789d1..80280447 100644 --- a/txcache/testutils_test.go +++ b/txcache/testutils_test.go @@ -1,7 +1,9 @@ package txcache import ( + cryptoRand "crypto/rand" "encoding/binary" + "math" "math/big" "math/rand" "sync" @@ -16,12 +18,43 @@ const oneBillion = oneMilion * 1000 const oneQuintillion = 1_000_000_000_000_000_000 const estimatedSizeOfBoundedTxFields = uint64(128) const hashLength = 32 +const addressLength = 32 var oneQuintillionBig = big.NewInt(oneQuintillion) // The GitHub Actions runners are (extremely) slow. const selectionLoopMaximumDuration = 30 * time.Second +var randomHashes = newRandomData(math.MaxUint16, hashLength) +var randomAddresses = newRandomData(math.MaxUint16, addressLength) + +type randomData struct { + randomBytes []byte + numItems int + itemSize int +} + +func newRandomData(numItems int, itemSize int) *randomData { + randomBytes := make([]byte, numItems*itemSize) + + _, err := cryptoRand.Read(randomBytes) + if err != nil { + panic(err) + } + + return &randomData{ + randomBytes: randomBytes, + numItems: numItems, + itemSize: itemSize, + } +} + +func (data *randomData) getItem(index int) []byte { + start := index * data.itemSize + end := start + data.itemSize + return data.randomBytes[start:end] +} + func (cache *TxCache) areInternalMapsConsistent() bool { internalMapByHash := cache.txByHash internalMapBySender := cache.txListBySender diff --git a/txcache/txCache_test.go b/txcache/txCache_test.go index 321f7b25..c96f7054 100644 --- a/txcache/txCache_test.go +++ b/txcache/txCache_test.go @@ -1,7 +1,6 @@ package txcache import ( - "crypto/rand" "errors" "fmt" "math" @@ -528,9 +527,6 @@ func TestBenchmarkTxCache_addManyTransactionsWithSameNonce(t *testing.T) { } host := txcachemocks.NewMempoolHostMock() - randomBytes := make([]byte, math.MaxUint16*hashLength) - _, err := rand.Read(randomBytes) - require.Nil(t, err) sw := core.NewStopWatch() @@ -543,7 +539,7 @@ func TestBenchmarkTxCache_addManyTransactionsWithSameNonce(t *testing.T) { sw.Start(t.Name()) for i := 0; i < numTransactions; i++ { - cache.AddTx(createTx(randomBytes[i*hashLength:(i+1)*hashLength], "alice", 42).withGasPrice(oneBillion + uint64(i))) + cache.AddTx(createTx(randomHashes.getItem(i), "alice", 42).withGasPrice(oneBillion + uint64(i))) } sw.Stop(t.Name()) @@ -560,7 +556,7 @@ func TestBenchmarkTxCache_addManyTransactionsWithSameNonce(t *testing.T) { sw.Start(t.Name()) for i := 0; i < numTransactions; i++ { - cache.AddTx(createTx(randomBytes[i*hashLength:(i+1)*hashLength], "alice", 42).withGasPrice(oneBillion + uint64(i))) + cache.AddTx(createTx(randomHashes.getItem(i), "alice", 42).withGasPrice(oneBillion + uint64(i))) } sw.Stop(t.Name()) @@ -577,7 +573,7 @@ func TestBenchmarkTxCache_addManyTransactionsWithSameNonce(t *testing.T) { sw.Start(t.Name()) for i := 0; i < numTransactions; i++ { - cache.AddTx(createTx(randomBytes[i*hashLength:(i+1)*hashLength], "alice", 42).withGasPrice(oneBillion + uint64(i))) + cache.AddTx(createTx(randomHashes.getItem(i), "alice", 42).withGasPrice(oneBillion + uint64(i))) } sw.Stop(t.Name()) @@ -597,9 +593,9 @@ func TestBenchmarkTxCache_addManyTransactionsWithSameNonce(t *testing.T) { // Thread(s) per core: 2 // Core(s) per socket: 4 // - // 0.000117s (TestBenchmarkTxCache_addManyTransactionsWithSameNonce/numTransactions_=_100) - // 0.003117s (TestBenchmarkTxCache_addManyTransactionsWithSameNonce/numTransactions_=_1000) - // 0.056481s (TestBenchmarkTxCache_addManyTransactionsWithSameNonce/numTransactions_=_5_000) + // 0.000120s (TestBenchmarkTxCache_addManyTransactionsWithSameNonce/numTransactions_=_100_(worst_case)) + // 0.002821s (TestBenchmarkTxCache_addManyTransactionsWithSameNonce/numTransactions_=_1000_(worst_case)) + // 0.062260s (TestBenchmarkTxCache_addManyTransactionsWithSameNonce/numTransactions_=_5_000_(worst_case)) } func newUnconstrainedCacheToTest() *TxCache {