-
-
Notifications
You must be signed in to change notification settings - Fork 318
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
getCommitteeAssignments #2717
getCommitteeAssignments #2717
Conversation
Code Climate has analyzed commit e756863 and detected 1 issue on this pull request. Here's the issue category breakdown:
View more on Code Climate. |
NOTE: noticed something may be off in my benchmark, so I'm going to double check it and put the results in the description once I'm done checking & benchmarking. |
Had to fix a bug (which the fix for is now in this PR), but the latest performance metrics of this PR are in the description. I could maybe go further with some of the optimizations (not sure just yet), but as I'm heading out for the day, I'm leaving what I got here for now until I can take a second look. |
Benchmarks should be the same because the code is fundamentally the same. You've replaced a for loop for a map and another for loop for a find index. The time complexity of the algorithm is the same, but maybe less obvious. It should even be slower because callback-based iterators are less optimized than for of loops (but I'm not sure). However a basic for loop will always be much faster. To make this faster you have to remove one loop and iterate the committee once instead of one time per validator. |
Current algorytm loops through the entire shuffled indices array for each validator. Not only that but it returns the committee array so it is looped again afterwards. for (const validatorIndex of validatorIndices) {
const committeeAssignment = (function () {
for (let slot = epochStartSlot; slot < epochStartSlot + SLOTS_PER_EPOCH; slot++) {
const committeeCount = this.getCommitteeCountAtSlot(slot);
for (let i = 0; i < committeeCount; i++) {
const committee = this.getBeaconCommittee(slot, i);
if (committee.includes(validatorIndex)) {
return {
validators: committee as List<number>,
committeeIndex: i,
slot,
};
}
}
}
})();
if (committeeAssignment) {
let validatorCommitteeIndex = -1;
let index = 0;
for (const i of readonlyValues(committeeAssignment.validators)) {
if (ssz.ValidatorIndex.equals(i, validator.index)) {
validatorCommitteeIndex = index;
break;
}
index++;
}
return {
pubkey: validator.pubkey,
validatorIndex: validator.index,
committeeLength: committeeAssignment.validators.length,
committeesAtSlot: epochCtx.getCommitteeCountAtSlot(committeeAssignment.slot),
validatorCommitteeIndex,
committeeIndex: committeeAssignment.committeeIndex,
slot: committeeAssignment.slot,
};
}
} A more reasonable approach would be to iterate the shuffled array once for all validatorIndices and not loop twice through the committee array |
@dapplion I followed your suggestion and got significant performance increases:
Now the solution just uses The benchmark test I used to get those numbers is in this PR. It also checks to make sure that |
packages/lodestar/test/unit/api/impl/validator/duties/attester.test.ts
Outdated
Show resolved
Hide resolved
packages/lodestar/test/perf/api/impl/validator/attester.test.ts
Outdated
Show resolved
Hide resolved
import {generateCachedState} from "../../../utils/state"; | ||
import {CheckpointStateCache} from "../../../../src/chain/stateCache"; | ||
import {Checkpoint} from "../../../../../types/phase0"; | ||
import {CachedBeaconState} from "@chainsafe/lodestar-beacon-state-transition"; | ||
|
||
describe("CheckpointStateCache perf tests", function () { |
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.
Mixup? This changes do not belong to this PR
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 was getting some errors after changing the yarn run test:perf
command to yarn run benchmark
, so I made changes here accordingly.
These are the current numbers in my local machine
Since getCommitteeAssignments() is something we should call twice per epoch I think it's good for now. The node will spend ~10ms every 6min doing executing this logic (0.003% rate). Note that prior to this PR, for 1000 validators the node will do 4.4ms x 1000 = 4 sec total. AKA not acceptable |
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.
Looks good to me, just a small note that if we use state.validators.persistent
frequently, it's maybe better to write a method in CachedValidatorList
so that we get through the proxy a single time
Good idea! Can you open an issue for it with more details? |
Resolves #2716 by implementing
getCommitteeAssignments
, a replacement ofassembleAttesterDuty
.current benchmark results:
The benchmark test I used to get those numbers is in this PR. It also checks to make sure that
getCommitteeAssignments
gets the same values that batchassembleAttesterDuty
previously did.Removed
assembleAttesterDuty
as well.