Skip to content

Performance characteristics

What determines the cost of running this library, not a number claiming to be it. A benchmark measures the machine it ran on — benchmark/latency.rb and benchmark/rescreen.rb say so in their own header comments, and print rather than commit, because a number fixed to one laptop says nothing about the hardware a deployment actually runs on. Nothing on this page is a committed or measured figure; run the commands below against your own corpus and your own machine before sizing anything.

For one measured example — a specific run, on one specific machine, useful for a rough sense of scale before running your own — see the Performance section of the README.

What each stage costs, and what changes it

Section titled “What each stage costs, and what changes it”
StageWhat it costsMeasure it with
Building the matcherPaid once, when a store is first read — at boot, or after a sync changes it. Never paid per query. Scales with corpus size: more entities and more aliases mean a longer build and a larger resident indexrake benchmark:index
Scoring one queryBounded by candidate_limit, not by corpus size — the index narrows a query to at most that many names before the scorer ever runs. A screening_threshold turns off the scorer’s more expensive comparisons for a candidate that cannot clear it, without changing which candidates clearrake benchmark:scorer, rake benchmark:latency
A full screening callMatcher#screen, end to end: fold the query, retrieve from the index, score, filter, sort, cap at screening_limit. What a service is sized fromrake benchmark:latency
Rescreening a book of businessThe book times what a diff actually changed, not the book times the whole corpus — see Rescreen. An empty diff scores nothingrake benchmark:rescreen
A sync where nothing changedOne conditional request per declared file; no download, no parserake benchmark:latency measures the parse, not the sync
The similarity algorithms themselvesFour comparisons per candidate name; the token-set ratio is the one an early exit buys the least, because a name that is a subset of another scores 1.0 at any lengthrake benchmark:similarity

RUBYOPT=--yjit before any of the above measures the same stage under YJIT, which is meaningfully cheaper for the scoring-heavy stages. Every command accepts no arguments and reaches no network — see each script’s own header comment in benchmark/ for what corpus it builds and why.

  • A matcher is immutable once built. Many threads screen through one index without a lock; a sync builds a new matcher rather than mutating the old one, so a request in flight finishes against one consistent list version. See Holding a client, and screening from many threads.
  • Threshold changes cost, never the answer. Every early exit in the scorer is a bound on what a pair of names could still reach, never an approximation of what it did reach, so a result at or above a threshold is exactly the result the same call with no threshold at all would return. rake benchmark:scorer scores every candidate twice — with and without a cutoff — and fails loudly if the two ever disagree.
  • candidate_limit trades recall for latency; screening_threshold trades recall for precision. Raising the limit costs milliseconds for candidates the scorer already sees everything that could clear a threshold among; lowering the threshold finds more names and returns more noise per query. See Configuration for both, and Accuracy for what moving the threshold costs in precision and recall, measured against the labeled set.
  • Rescreening scales with what moved, not with the book. A book of ordinary customer names shares almost no feature with the handful of records a daily diff touches, so almost none of it reaches the scorer at all — the cost is dominated by describing each subject’s name, not by comparing it to anything.