Resource consumption bottlenecks #11385
turbolay
started this conversation in
General & Publications
Replies: 2 comments
-
I'm pretty sure this is not the root of the problem, but the first thing that comes to my mind whenever I see these kind of lookups in a hot loop code path is to use a dictionary and reduce the O(n) lookups to O(1). Just an improvement that can be done amongst many others. |
Beta Was this translation helpful? Give feedback.
0 replies
-
Bottlenecks in Bird Eye View:1. Wallet StartupGoal: Instant 2. Wallet LoadGoal: large wallet load to be instant 3. Wallet Memory UsageGoal: large wallet to use under 1 GB memory |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Background
It's simple: We currently keep everything in memory (except filters) and create potentially insanely big data structures that we access through algorithms of
O(N)
or evenO(N²)
. I will take the coordinator's wallet for my examples because it's perfect: It participated in every coinjoins (18.500 according to dumplings). Using Wasabi for such big wallets is really annoying for various reasons: Long synchronization, constant lag, insane memory usage... This note aims to identify the bottlenecks that create important resource consumption and see what we can do about them. It's more or less ordered by bigger to lower impact. I will edit as I discover new ones.BuildHistorySummary
This method is a computation and memory monster. It creates new objects for every input and every output of every transaction. It has no pagination, no "checkpoints". It reorders all txs at every iteration. Everything is recomputed at every update. The easy way to see its impact on a wallet such as coordinator's wallet is to run it first on the Daemon, check how "fast" the wallet is usable and its memory usage (around 6 GB), then run the same wallet with the GUI and see how it's a nightmare to launch and check memory usage (around 13 GB). It single-handedly makes the wallet close to unusable. I would suggest to create a pagination of some sort. Also, it makes the GUI unresponsive during the whole time of its execution.
Look at this case, I'm trying to open my wallet. The history took a whopping 6 minutes to build (!!!), and once it finally opened, it was already invalidated by a filter that I received while building it 😆
![CleanShot 2023-08-28 at 01 32 54@2x](https://private-user-images.githubusercontent.com/16029533/263629357-eb39f39e-743a-482c-a8f7-538dd2d363f3.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzk0OTU3MTQsIm5iZiI6MTczOTQ5NTQxNCwicGF0aCI6Ii8xNjAyOTUzMy8yNjM2MjkzNTctZWIzOWYzOWUtNzQzYS00ODJjLWE4ZjctNTM4ZGQyZDM2M2YzLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjE0VDAxMTAxNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTI0ZTQxNWIyOGIxNzlmZDZiYWNiOTI2ZGViY2IzZGE0MDFjZWY1NjIzOGYxZjY4ZGM4ZjM0MzYxNzQ0ZWFmNzMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.oW816rucEmToE6ION0fIRicBESOp7rqRX1805by7_P8)
In general, it's updated all. the. time. Even several times in parallel. It consumes all my CPU, and bloat everything else in the wallet.
![CleanShot 2023-08-28 at 01 38 15@2x](https://private-user-images.githubusercontent.com/16029533/263631311-5ee08828-a20f-4530-abdf-3a171e8f519e.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzk0OTU3MTQsIm5iZiI6MTczOTQ5NTQxNCwicGF0aCI6Ii8xNjAyOTUzMy8yNjM2MzEzMTEtNWVlMDg4MjgtYTIwZi00NTMwLWFiZGYtM2ExNzFlOGY1MTllLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjE0VDAxMTAxNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWRmZjQxYTRhNzM0NGU5YzdmYTY5MzE3YWQ5MTViOTE4YzhjNGE2NmE5MmVjYjk5ZjFlM2VjODI0ZGJhOTAxNWEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.HnstBrK1TdJA6aSk8-5Wgt9nfPDismxugXqUbUhu950)
I wouldn't be surprised if the difference in memory usage from v2.0.3 to v2.0.4 lies in here.
AllTransactionsStore
18.500 coinjoins txs, each with a whopping 250 inputs and 250 outputs, out of which we store absolutely every information (
SmartTransaction
data structure). The serialized data structure in the file system is about 700 MB, so about 37.8 kb of data for each tx. Once deserialized, a whopping ~3.5 GB is used for this data structure, about 190 kb of data per tx. Therefore, every month, the coordinator wallet takes ~300 Mb more memory to run, only accounting for this data structure.In many cases, we are accessing or updating a single tx, but let's see when exactly we are using all the txs:
UpdateLabels
to get every label in theLabelEntryDialogViewModel
Initial Transaction Processing
to loop through all our stored txs inWallet.LoadWalletState
ReleaseToMempoolFromBlock
TransactionStore.InitializeTransactionsNoLockAsync
, we write all the transactions to the file systemWell, all of these cases have something in common: They don't justify having all transactions in memory. In fact, it's really similar to what we are doing with the filters, and the SQLite database would be a perfect way to store all of these transactions.
We could use the same serialization that is already used for the file system, with columns for each
:
separator. All cases would be easily covered: Getting all labels is trivial as they have their own column, same goes forReleaseToMempoolFromBlock
. Getting all txs for initial transaction processing, transaction history or for tests could be done using ayield
similar to what is done for filters.Getting rid of this object wouldn't instantly free all the memory it's used because other data structures referencing them such as
Coins
AsAllCoinsView
This is a data structure that is not that big, it will have one entry per UTXO that was ever received by the wallet. The problem is the complexity. Each time it's called, it's enumerated twice. It's called twice for every tx processed, and after call there is an extra enum so 2 more. Of course, every tx is processed every time we open the wallet. Therefore, while opening the coordinator's wallet,
AsAllCoinsView
is enumerated...~18.500*2*2*2=150.000 times
. This takes a lot of time. I won't extend too much on this because #11298 does a great job fixing it.CoinsByOutpoint
This is a
Dictionary<OutPoint, HashSet<SmartCoin>>
. TheKeys
are everyPrevout
of everyInput
of everyTx
incoming to our wallet, and the values areHashSets
of everyOutput
belonging to the wallet in these transactions (so every UTXOs that we have is presentnumber of inputs in its creating TX
times in this cache).Let's take a coinjoin with 250 inputs and 5 outputs belonging to my wallet. This will create 250
KeyValuePair
in the dictionary, each having aHashSet
of 5 elements. So 18.500 coinjoins of 250 inputs = 4.625.000KeyValuePair
and the same number ofHashSets
. This is an ever-growing cache as we only remove in case of double spends.So one might say, "Ok but these are only references". This is true, only references to already existing objects. But what about the overhead? It's huge. This object alone takes ~1.2 GB of memory for that number of coinjoins. Also, creating all these objects take quite an extensive amount of time.
One solution is to stop checking for replacements for transactions that are already mature, this results in a dramatic reduction of the data stored in this object. This is the approach of #11374. The idea is that the nodes should reject double spends anyway, and the client shouldn't have to take care of that. This can have consequences I'm not sure about.
Another solution is to try to reduce the size of this cache, and specifically the overhead of the hash sets, by using the same
HashSet
for eachprevout
from the same transactions. This is the approach of #11384Beta Was this translation helpful? Give feedback.
All reactions