Skip to content

perf(delphi): resolve group member rows once per group in _compute_group_votes - #2691

Open
eastagiletracker wants to merge 1 commit into
compdemocracy:edgefrom
eastagiletracker:agile-board/vectorize-group-votes
Open

perf(delphi): resolve group member rows once per group in _compute_group_votes#2691
eastagiletracker wants to merge 1 commit into
compdemocracy:edgefrom
eastagiletracker:agile-board/vectorize-group-votes

Conversation

@eastagiletracker

Copy link
Copy Markdown

This PR proposes resolving each group's member rows once per group in Conversation._compute_group_votes rather than once per comment and vote type, removing the O(groups × comments × members) rescan that runs twice on every math tick (Fixes #2587). We include this PR work along with a full history of your repo at https://eastagiletracker.com/projects/174. You can sign in with your GitHub ID to claim ownership of the project.

What changed and why

_compute_group_votes produced its per-group A/D/S counts through a nested count_votes_for_group(group_id, comment_id, vote_type) helper. Every call searched unfolded for the group, re-resolved every member's row with rating_mat.index.get_loc, and then read a single column out of rating_mat.values. Because the helper is invoked three times for each comment in each group, Index.get_loc runs 3 × groups × comments × members times per call — and the call itself happens twice per tick, once from _compute_comment_priorities inside recompute() and once from _compute_group_aware_consensus on the DynamoDB write path.

Reproduced on edge at 5089c6b, using the committed biodiversity dataset:

$ cd delphi && PYTHONPATH=. python -c "
import sys, time, logging; logging.disable(logging.INFO); sys.path.insert(0, 'tests')
from common_utils import load_votes
from polismath.regression import get_dataset_files
from polismath.conversation.conversation import Conversation
files = get_dataset_files('biodiversity', blob_type='incremental')
conv = Conversation('bio').update_votes(load_votes(files['votes'])).recompute()
print('matrix', conv.rating_mat.shape, 'groups', len(conv.group_clusters))
t = time.perf_counter(); conv._compute_group_votes(); print('_compute_group_votes: %.3fs' % (time.perf_counter() - t))
"
matrix (536, 314) groups 2
_compute_group_votes: 0.119s

That is 536 participants and 314 comments — small next to a real conversation — and the call already accounts for roughly 45% of the 0.26s recompute() it sits inside.

The fix hoists the member-row lookup to once per group and takes the three counts as column-wise reductions over that group's slice of the vote matrix, so the matrix is read once per group instead of once per comment. Nothing about the result moves: same str(group_id) keys, same comment keys, same n-members, same plain-int counts, and S still counts every cast vote including PASS. Same measurement after the change reports 0.001s; on a synthesized 33000 × 783 matrix with 5 groups — the shape your large-conversation benchmark uses — it goes from 19.2s to 0.34s, with both implementations asserted to return byte-identical structures.

Verification, all on the same tree:

  • delphi/tests/test_group_votes.py is new. Two of its cases pin the complexity by counting Index.get_loc calls rather than wall-clock time, so they are deterministic: on edge they fail (assert 960 == 3840 — the count quadruples when the comment count quadruples), and they pass here. The other four cases are correctness controls that hold on both trees: reference-count equivalence, PASS counted in S but not in A/D, empty groups reporting every comment at zero, and counts being plain int rather than numpy scalars.
  • The full delphi suite was run before and after with no new failures: baseline 402 passed, 20 skipped, 47 xfailed, 2 failed, after 408 passed, 20 skipped, 47 xfailed, 2 failed — the same two pre-existing failures both times, in the two modules that need optional deps (torch, umap-learn) absent from this sandbox. Every parity case that reads group votes, group-aware consensus, comment priorities or repness — 43 of them, including the Clojure math-blob comparisons — is green.

One note on merge order: #2689 moves this file to math/polismath/conversation/conversation.py without changing its contents, so if that lands first the same hunk applies at the new path.

How this was managed

We imported your issues and pull requests into a live agile board and used it to manage this work: the imported story for #2587 is at https://eastagiletracker.com/projects/174/stories/44546, and the board it lives on is at https://eastagiletracker.com/projects/174.

board

If you'd rather not receive contributions like this, reply no-more-prs on this pull request and we won't open any further ones on your repositories.


Lawrence W. Sinclair
CEO / East Agile
linkedin.com/in/lwsinclair/
eastagile.com

`_compute_group_votes` resolved each group member's matrix row inside a
helper that was called once per (comment, vote-type), so every comment
rescanned the whole group: `Index.get_loc` ran `3 x groups x comments x
members` times per call, and the call happens twice per math tick — once
from `_compute_comment_priorities` inside `recompute()`, once from
`_compute_group_aware_consensus` on the DynamoDB write path.

Hoist the row lookup to once per group and take the A/D/S counts as three
column-wise reductions over that group's slice of the vote matrix. Output
is unchanged: same keys, same plain-int counts, same PASS-inclusive S.

On the committed biodiversity dataset (536 x 314, 2 groups) the call goes
from 0.113s to 0.0009s; on a synthesized 33000 x 783, 5-group matrix from
19.2s to 0.34s.
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.

delphi: _compute_comment_priorities recomputes group votes on every tick (O(groups×comments×members))

1 participant