Skip to content
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

Merged
merged 44 commits into from
Jul 1, 2021
Merged

getCommitteeAssignments #2717

merged 44 commits into from
Jul 1, 2021

Conversation

3xtr4t3rr3str14l
Copy link
Contributor

@3xtr4t3rr3str14l 3xtr4t3rr3str14l commented Jun 16, 2021

Resolves #2716 by implementing getCommitteeAssignments, a replacement of assembleAttesterDuty.

current benchmark results:

get attester duties
================================================================
new way: getCommitteeAssignments                                      0.6062110 ops/s      1.649591  s/op      2 runs    3.299 s
new way (plus index2pubkey): getCommitteeAssignments                  0.7766266 ops/s      1.287620  s/op      2 runs    2.576 s
old way: assembleAttesterDuty batch                                   0.2985871 ops/s      3.349106  s/op      2 runs    6.698 s
    ✓ performance comparison (12678ms)

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 batch assembleAttesterDuty previously did.

Removed assembleAttesterDuty as well.

@codeclimate
Copy link

codeclimate bot commented Jun 16, 2021

Code Climate has analyzed commit e756863 and detected 1 issue on this pull request.

Here's the issue category breakdown:

Category Count
Complexity 1

View more on Code Climate.

@3xtr4t3rr3str14l 3xtr4t3rr3str14l marked this pull request as draft June 16, 2021 22:35
@3xtr4t3rr3str14l
Copy link
Contributor Author

3xtr4t3rr3str14l commented Jun 16, 2021

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.

@3xtr4t3rr3str14l 3xtr4t3rr3str14l changed the title implement batch getAttesterDuties implement batch assembleAttesterDuties Jun 16, 2021
@3xtr4t3rr3str14l
Copy link
Contributor Author

3xtr4t3rr3str14l commented Jun 16, 2021

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.

@dapplion
Copy link
Contributor

dapplion commented Jun 17, 2021

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.

@dapplion
Copy link
Contributor

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

@3xtr4t3rr3str14l
Copy link
Contributor Author

3xtr4t3rr3str14l commented Jun 17, 2021

@dapplion I followed your suggestion and got significant performance increases:

number of runs:  50
assembleAttesterDuty batch performance avg:  3332.42
getCommitteeAssignments performance avg:  4.22

Now the solution just uses getCommitteeAssignments (new epochCtx batch function) directly and does the work that running assembleAttesterDuty in batch previously did.

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 batch assembleAttesterDuty previously did.

@3xtr4t3rr3str14l 3xtr4t3rr3str14l changed the title implement batch assembleAttesterDuties getCommitteeAssignments Jun 17, 2021
@3xtr4t3rr3str14l 3xtr4t3rr3str14l marked this pull request as ready for review June 17, 2021 23:36
@3xtr4t3rr3str14l 3xtr4t3rr3str14l marked this pull request as draft June 17, 2021 23:42
@3xtr4t3rr3str14l 3xtr4t3rr3str14l marked this pull request as ready for review June 17, 2021 23:42
wemeetagain
wemeetagain previously approved these changes Jun 28, 2021
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 () {
Copy link
Contributor

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

Copy link
Contributor Author

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.

@dapplion
Copy link
Contributor

dapplion commented Jun 30, 2021

This seems to speed up getCommitteeAssignments 2x for small # of validators, 30x for large # of validators, so it may be "good enough"?

These are the current numbers in my local machine

    ✓ getCommitteeAssignments - req 1 vs - 200000 vc                      225.6465 ops/s    4.431711 ms/op        -       1024 runs   4.54 s
    ✓ getCommitteeAssignments - req 100 vs - 200000 vc                    141.3410 ops/s    7.075087 ms/op        -       1024 runs   7.25 s
    ✓ getCommitteeAssignments - req 1000 vs - 200000 vc                   124.7096 ops/s    8.018632 ms/op        -       1024 runs   8.25 s

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

Copy link
Contributor

@twoeths twoeths left a 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

@dapplion
Copy link
Contributor

dapplion commented Jul 1, 2021

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?

@dapplion dapplion merged commit b2c38d1 into master Jul 1, 2021
@dapplion dapplion deleted the P0/#2716 branch July 1, 2021 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

implement batch getCommitteeAssignment
4 participants