New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
mempool: Keep cache hashmap and linked list in sync #2188
Conversation
3b258d7
to
ab8fddc
Compare
This removes bugs with the linked list being full, but hashmap empty
ab8fddc
to
27b48d9
Compare
Codecov Report
@@ Coverage Diff @@
## develop #2188 +/- ##
==========================================
- Coverage 62.28% 61% -1.29%
==========================================
Files 212 193 -19
Lines 17565 15761 -1804
==========================================
- Hits 10941 9615 -1326
+ Misses 5718 5336 -382
+ Partials 906 810 -96
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be great to create a small benchmark for the mempool and compare old/new implementations.
mempool/mempool.go
Outdated
@@ -528,16 +527,26 @@ func (cache *mapTxCache) Push(tx types.Tx) bool { | |||
// but deleting a non-existent element is fine |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment can be removed now
mempool/mempool.go
Outdated
return true | ||
} | ||
|
||
// Remove removes the given tx from the cache. | ||
func (cache *mapTxCache) Remove(tx types.Tx) { | ||
cache.mtx.Lock() | ||
delete(cache.map_, string(tx)) | ||
stx := string(tx) | ||
// TODO: Can we get value and index at the same time? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aww okay. #postlaunch it may be worth experimenting with a "prehashed input hashmap. (Alot simpler than it sounds) and then we just hash once with the same fast hash.
The benefit of this change is space savings + fixing bugs with cache eviction. Computation time will increase due to usage of the clist, so making a benchmark is a good idea. Is there a good way to benchmark memory taken up? Just println unsafe size of? |
|
:o cool, didn't know about that |
before:
after:
So it looks like we're getting a performance hit due to the concurrent list. However this performance hit is neglible compared to the signature verification of saving a checkTx, and we are now saving memory from elements we remove from the cache. (The before case didn't remove it at all from the linked list. If we used the O(N) method to do that properly, it would certainly be slower) |
I tend to agree. Plus, if there are usually no "bad" txs in the network (e.g. private deployment), cache can be always disabled. |
mempool/mempool.go
Outdated
map_ map[string]struct{} | ||
list *list.List // to remove oldest tx when cache gets too big | ||
map_ map[string]*clist.CElement | ||
list *clist.CList // to remove oldest tx when cache gets too big |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there some reason we needed to switch to Clist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. CheckTxs are called in parallel, and can have different execution times. I can describe problematic situations more if you'd like. (I ran through a couple in my head that I thought would eventually cause big problems before making the clist switch)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But is there some new requirement that only now we need a clist? or you think we always needed it?
CheckTx actually don't run in parallel, they run sequentially, but responses come back asynchronously (though in order)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh your right, we don't need it here. The reason we don't need it (and something I failed to consider earlier) is that we have a mutex on all deletions and additions. I forgot that we needed that for the hashmap still. I'll switch it back, thanks for pointing this out :D
@@ -524,20 +523,26 @@ func (cache *mapTxCache) Push(tx types.Tx) bool { | |||
if cache.list.Len() >= cache.size { | |||
popped := cache.list.Front() | |||
poppedTx := popped.Value.(types.Tx) | |||
// NOTE: the tx may have already been removed from the map | |||
// but deleting a non-existent element is fine |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so we knew we weren't removing from the list in .Remove but we thought it was ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing noone considered the lingering memory or the issues with how it affects cache eviction of good txs. (Full linked list, empty hashmap)
Now that we're back to normal lists, here are the new benchmarks:
basically just adds 20ns to removal time. |
a844c4a
to
3ed0d72
Compare
3ed0d72
to
11e98cd
Compare
This removes bugs with the linked list being full, but hashmap empty.
Ref #2180