-
Notifications
You must be signed in to change notification settings - Fork 215
Proofs pool improvements #6878
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
Proofs pool improvements #6878
Conversation
| func (p *proofNonceBucket) insertInNew(proof data.HeaderProofHandler) { | ||
| p.insert(proof) | ||
| p.maxNonce = proof.GetHeaderNonce() | ||
| } | ||
|
|
||
| func (p *proofNonceBucket) insertInExisting(proof data.HeaderProofHandler) { | ||
| p.insert(proof) | ||
|
|
||
| if proof.GetHeaderNonce() > p.maxNonce { | ||
| p.maxNonce = proof.GetHeaderNonce() | ||
| } | ||
| } | ||
|
|
||
| func (p *proofNonceBucket) insert(proof data.HeaderProofHandler) { | ||
| p.proofsByNonce = append(p.proofsByNonce, &proofNonceMapping{ | ||
| headerHash: string(proof.GetHeaderHash()), | ||
| nonce: proof.GetHeaderNonce(), | ||
| }) | ||
| } |
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.
why not move
if proof.GetHeaderNonce() > p.maxNonce {
p.maxNonce = proof.GetHeaderNonce()
}into insert method and only keep one method for all 3 operations?
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.
right, pushed
| @@ -0,0 +1,66 @@ | |||
| package proofscache_test | |||
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.
If we move the test to package proofscache, maybe we can drop some code from the export_test file. Can also stay as it is.
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 would keep it in _test package since there are other useful functions from test
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.
and we plan to make this component more generic and to export it in other packages as well (for headers pool)
| func Benchmark_AddProof_Bucket10_Pool1000(b *testing.B) { | ||
| benchmarkAddProof(b, 10, 1000) | ||
| } | ||
|
|
||
| func Benchmark_AddProof_Bucket100_Pool10000(b *testing.B) { | ||
| benchmarkAddProof(b, 100, 10000) | ||
| } | ||
|
|
||
| func Benchmark_AddProof_Bucket1000_Pool100000(b *testing.B) { | ||
| benchmarkAddProof(b, 1000, 100000) | ||
| } |
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.
Maybe also add some output / statistics in the PR description.
| mutProofsByNonce sync.RWMutex | ||
| proofsByNonceBuckets []*proofNonceBucket |
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.
Alternatively, maybe sync.Map is useful?
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.
the way proofsByNonceBuckets is implemented right now, it needs order, so it doesn't fit very well
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.
Sorry, you are right. No maps here 👍
| func (p *proofNonceBucket) insertInNew(proof data.HeaderProofHandler) { | ||
| p.insert(proof) | ||
| p.maxNonce = proof.GetHeaderNonce() | ||
| } | ||
|
|
||
| func (p *proofNonceBucket) insertInExisting(proof data.HeaderProofHandler) { | ||
| p.insert(proof) | ||
|
|
||
| if proof.GetHeaderNonce() > p.maxNonce { | ||
| p.maxNonce = proof.GetHeaderNonce() | ||
| } | ||
| } |
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.
Can we merge them into a single insert function or does it break the logic? If it was done for the purpose of optimizing the nonce check - the if proof.GetHeaderNonce() > p.maxNonce { is just a quick comparison (should be negligible).
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.
Outdated comment, fixed upon Sorin's suggestion.
| pc.mutProofsByHash.Lock() | ||
| defer pc.mutProofsByHash.Unlock() |
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.
The caller function also locks (sorry if I'm mistaken).
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.
there are 2 separate locks
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.
Indeed, my bad / overlooked!
| for _, bucket := range pc.proofsByNonceBuckets { | ||
| if nonce > bucket.maxNonce { | ||
| wg.Add(1) | ||
|
|
||
| proofsByNonce := make([]*proofNonceMapping, 0) | ||
| go func(bucket *proofNonceBucket) { | ||
| pc.cleanupProofsInBucket(bucket) | ||
| wg.Done() | ||
| }(bucket) |
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.
Maybe allow the cleanup to happen synchronously? I see the workload is lock, delete from map in a loop, unlock. Do we have a speed gain using concurrency in this context (since map deletions are not quite subject to parallelization)?
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.
removed concurrency here
|
|
||
| pc.mutProofsCache.Lock() | ||
| defer pc.mutProofsCache.Unlock() | ||
| pc.mutProofsByNonce.Lock() |
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.
If cleanup procedure does not happen extremely often (~rare event), maybe both locks can happen one near the other - e.g. lock & unlock mutProofsByHash for all buckets at once (I think this was Sorin's internal suggestion).
| defer pc.mutProofsCache.Unlock() | ||
| pc.insertProofByNonce(proof) | ||
|
|
||
| pc.proofsByNonce = append(pc.proofsByNonce, &proofNonceMapping{ | ||
| headerHash: string(proof.GetHeaderHash()), | ||
| nonce: proof.GetHeaderNonce(), | ||
| }) | ||
| pc.mutProofsByHash.Lock() | ||
| pc.proofsByHash[string(proof.GetHeaderHash())] = proof | ||
| pc.mutProofsByHash.Unlock() |
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.
Having these two locks separated does not seem to be an optimization (at least, not at a first glance). Since:
lock A
do ~trivial (& with short duration) logic around insertion in bucket
unlock A
lock B
do ~trivial (& with short duration) operation of adding a map entry
unlock B
Is not necessarily better than:
lock C
do ~trivial (& with short duration) logic around insertion in bucket
do ~trivial (& with short duration) operation of adding a map entry
unlock C
Especially if extreme concurrency (e.g. a lot of new insertions bootstrapped during each of the ~trivial two parts) isn't expected.
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.
right, updated to keep only one mutex
|
|
||
| type proofNonceBucket struct { | ||
| maxNonce uint64 | ||
| proofsByNonce []*proofNonceMapping |
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.
maybe use a map then the upsert would not need to iterate
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.
changed to use map
| return len(p.proofsByNonce) | ||
| } | ||
|
|
||
| func (p *proofNonceBucket) isFull() bool { |
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 would use fixed size and deterministic range for each bucket.
e.g a proof with nonce x should be added into the bucket with first nonce x/bucketSize * bucketSize (integer division)
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.
changed to use fixed size buckets
| proofsByNonce []*proofNonceMapping | ||
| proofsByHash map[string]data.HeaderProofHandler | ||
| mutProofsCache sync.RWMutex | ||
| proofsByNonceBuckets []*proofNonceBucket |
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 think this can be a map as well with the key the lowest nonce that the bucket would hold
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.
changed to use sync.Map
|
|
||
| headBucket := pc.proofsByNonceBuckets[0] | ||
|
|
||
| if headBucket.isFull() { |
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.
in case of syncing node this will cause it to keep accumulating new nonces in each old nonces buckets, which will delay their cleanup.
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.
new model with range buckets should cover this case
| mutProofsCache sync.RWMutex | ||
| proofsByNonceBuckets []*proofNonceBucket | ||
| bucketSize int | ||
| proofsByNonceBuckets sync.Map |
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.
proofNonceMapping struct not used anymore
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.
👍 deleted it
|
|
||
| pc.proofsByNonceBuckets = buckets | ||
| for _, key := range bucketsToDelete { | ||
| pc.proofsByNonceBuckets.Delete(key) |
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.
this is already done on L86
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.
right, old code; removed
Reasoning behind the pull request
Testing procedure
Pre-requisites
Based on the Contributing Guidelines the PR author and the reviewers must check the following requirements are met:
featbranch created?featbranch merging, do all satellite projects have a proper tag insidego.mod?