Skip to content

Stop conflate_synonyms computing output it then discards - #985

Draft
gaurav wants to merge 3 commits into
mainfrom
fix/conflate-synonyms-discarded-work
Draft

Stop conflate_synonyms computing output it then discards#985
gaurav wants to merge 3 commits into
mainfrom
fix/conflate-synonyms-discarded-work

Conversation

@gaurav

@gaurav gaurav commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

conflate_synonyms() drives two of Babel's most expensive rules — geneprotein_conflated_synonyms (15,719 s, 98 GB max RSS) and drugchemical_conflated_synonyms (9,949 s) — together about 8.5% of the pipeline's summed rule wall time, both ~99% CPU-bound on one core.

get_logger() defaults to logging.INFO, so logger.debug() output is suppressed — but an f-string argument is still evaluated in full before the call. Four sites were paying for results nobody reads.

The four sites

  • :143 — the whole accumulated synonyms_to_conflate structure serialised with json.dumps() to feed one suppressed debug line. That structure is the 98 GB of resident memory.
  • :260-261 — every output record serialised twice more for suppressed debug lines, once with indent=2, on top of the json.dumps() that actually writes it.
  • :135-140 — every input line recomputed a bl_types set over every synonym accumulated so far for its preferred ID — quadratic in clique size — and used it only in a suppressed debug line. It also rebound synonym inside the read loop, shadowing the loop variable.
  • :168, :170, :172-174 — the innermost step-3 loops logged at INFO, one of them formatting an entire synonym list per synonym.

Remaining logs take lazy %s arguments so nothing is formatted unless the level is enabled.

Measured

Over the local anatomy build's synonyms and compendia, with conflation groups of 2, 8 and 32 members: 1.33×, 3.11× and 1.39× faster, output identical every time.

Those inputs are three orders of magnitude smaller than the real gene/protein ones, so they do not predict the production figure — the site that should dominate there is the whole-structure json.dumps, which scales with a structure this data barely populates. Worth re-measuring on a real run.

Tests, and a pre-existing bug left unfixed

This adds the first tests this function has had. Records are copied verbatim from a local anatomy build; only the conflation file is authored, since a conflation pairing anatomy cliques does not occur in a real build (it stands in for GeneProtein.txt, same JSONL shape).

The third test pins a data-loss bug found while writing them, deliberately left unfixed here because this commit is meant to be behaviour-preserving:

ids (synonymconflation.py:87) is a one-shot map object. The outer loop searching for a conflated identifier consumes it, so the inner loop that registers the clique sees only what came after the match. The matching identifier — the one the conflation file names, and the one step 3 looks up — is therefore never registered, so a synonym record keyed on a non-leader identifier of a conflated clique is silently dropped rather than merged.

Verified identical on origin/main, so it is pre-existing. The test asserts the wrong-but-current behaviour with an invert-when-fixed comment, per tests/CLAUDE.md. It needs a tracking issue — I did not file one, and the test comment says so rather than pointing at a number that does not exist.

🤖 Generated with Claude Code

conflate_synonyms() drives two of Babel's most expensive rules --
geneprotein_conflated_synonyms (15,719 s, 98 GB max RSS) and
drugchemical_conflated_synonyms (9,949 s), together about 8.5% of the
pipeline's summed rule wall time, both ~99% CPU-bound on one core.

get_logger() defaults to logging.INFO, so logger.debug() output is suppressed
-- but an f-string argument is still evaluated in full before the call. Four
sites were paying for results nobody reads:

  - The whole accumulated synonyms_to_conflate structure was serialised with
    json.dumps() to feed one suppressed debug line. That structure is what the
    98 GB of resident memory is.
  - Every output record was serialised twice more for suppressed debug lines,
    once with indent=2, on top of the json.dumps() that actually writes it.
  - Every input line recomputed a bl_types set over every synonym accumulated
    so far for its preferred ID -- quadratic in clique size -- and used it only
    in a suppressed debug line. It also rebound `synonym` inside the read loop,
    shadowing the loop variable.
  - The innermost step 3 loops logged at INFO, one of them formatting an entire
    synonym list per synonym.

The remaining logs take lazy %s arguments so nothing is formatted unless the
level is enabled. Measured over the local anatomy build's synonyms and
compendia (bench script not committed; it needs a build tree), with conflation
groups of 2, 8 and 32 members: 1.33x, 3.11x and 1.39x faster, output identical
every time. Those inputs are three orders of magnitude smaller than the real
gene/protein ones, so they do not predict the production figure -- the site
that should dominate there is the whole-structure json.dumps, which scales with
a structure this data barely populates.

Also adds the first tests this function has had. The third one pins a data-loss
bug found while writing them and deliberately left unfixed here, since this
commit is meant to be behaviour-preserving: `ids` (synonymconflation.py:87) is a
one-shot map object, so the outer loop searching for a conflated identifier
consumes it and the inner loop that registers the clique sees only what came
after the match. The matching identifier -- the one the conflation file names
and the one step 3 looks up -- is never registered, so a synonym record keyed on
a non-leader identifier of a conflated clique is silently dropped rather than
merged. Verified identical on origin/main, so it is pre-existing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gaurav and others added 2 commits July 30, 2026 01:30
The test asserting that conflate_synonyms drops synonyms keyed on a non-leader
identifier said the tracking issue was not filed yet. It is now #989, so the
invert-when-fixed instruction has something to point at.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
get_logger() defaults to INFO, so debug output is suppressed -- but the f-string
argument is evaluated in full before the call. In a per-record loop that makes a
suppressed debug line a real cost, and json.dumps() inside one is unbounded.

This cost conflate_synonyms a serialisation of the entire accumulated structure
behind its 98 GB peak, plus two more per output record, all discarded. It is not
obvious from reading the call site, it recurs, and it is cheap to grep for once
you know to look -- so it belongs in AGENTS.md next to the logging rule rather
than only in this branch's commit message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant