Repository navigation
Prune fuzzy candidates using Jaro similarity bounds - #451
Draft
oliver-kriska wants to merge 1 commit into
Draft
oliver-kriska wants to merge 1 commit into
oliver-kriska wants to merge 1 commit into
Conversation
Prepare grapheme counts lazily and skip candidates that cannot reach the threshold or improve on the best match. Keep platform Jaro scoring for survivors, the original traversal order and first-winner tie behavior. Retain the full scan for empty or malformed input so pruning cannot hide scorer exceptions. Cover inclusive thresholds, repeated Unicode graphemes, ties, zero scores and malformed input through the merge API.
This was referenced Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Faster fuzzy merging without changing match selection
Related to #440. The expensive part of fuzzy merging is scoring every old message for each unmatched message, not constructing the matcher closure. This PR skips candidates whose maximum possible Jaro similarity is below the threshold or the best score already found. Surviving candidates still use
String.jaro_distance/2; this does not substitute an approximate matcher or change the fuzzy threshold.Lengths and grapheme frequencies are prepared once per PO merge, lazily when a nonempty, valid fuzzy query needs them. The bounds assume no transpositions and at most as many matching graphemes as the strings have in common. They use graphemes rather than bytes or codepoints, count repeated graphemes, and preserve the scorer's floating-point addition order. Candidate traversal order and the first winner on equal scores are retained. Exact matches and
--no-fuzzydo not build the index. All old messages remain eligible, including ones already used by another match.Empty queries use the original scan rather than preparing the catalog. Malformed UTF-8 on either side also uses the original full scan, including after candidate preparation: pruning or stopping at a perfect match must not suppress platform Jaro exceptions. Equality and empty-string fast paths remain the scorer's responsibility.
Measurements
Baseline: upstream b2c62ff. Apple M4 Pro, Elixir 1.20.4 / OTP 29.0.5,
MIX_ENV=prod, four schedulers. These are medians of seven alternating baseline/candidate pairs after an untimed warmup, using fresh input files for every invocation. Timings include the complete merge task: parsing, preparing candidates, merging, pruning references, composing and writing PO files. They exclude VM startup, compilation and copying the fixtures.--no-fuzzycontrolOn Elixir 1.19.5 / OTP 28.4.1 (the same major versions as the issue reporter), with the same methodology:
The large fixtures expand a private application's real catalog using unique msgid suffixes, retaining its translations, plurals, contexts and references. The real-change row replays an actual before/after catalog snapshot without generating strings. This is not the reporter's catalog, and the locale count and number of changed messages in that report are unknown. The machine had other work running, so these are indicative paired measurements, not guaranteed timings or a reproduction of the reported six-minute merge. The control results do not establish a speedup.
Separately traced Jaro-call counts fell from 779,328 to 6,096 for the real change, and from 2,590,000 to 3,128 for the 25,900-message/100-edit case. Allocated heap words inside
merge/5for the latter fell from 6.63 billion to 98.7 million; these are cumulative allocations, not peak memory. Profiling the 25,900-message/10-edit search shows the remaining work is mostly preparing grapheme counts and checking bounds rather than running Jaro comparisons.Equivalence and limits
mix test --warnings-as-errors(197 passed: 5 doctests, 192 tests), formatting and whitespace checks. Kept tests target inclusive thresholds, repeated/Unicode graphemes, best-match/tie behavior, zero thresholds and platform handling of malformed UTF-8.This trades temporary per-catalog indexing memory for fewer expensive comparisons. Histograms remain live until the merge finishes; peak memory was not measured. Worst-case scanning remains quadratic in old messages × unmatched new messages. Candidates with identical grapheme histograms can all survive the bounds, making the extra checks slower than the baseline. It also does not change extraction's timeout or compilation behavior, and does not claim to resolve every cause of the timeout in issue 440. Keeping this as a draft for feedback on the complexity/performance tradeoff.