Skip to content

Commit 46fdc3e

Browse files
derek73claude
andcommitted
Put a non-ASCII shape in the scaling benchmark (#308)
Every unit in _SHAPES was pure ASCII, so script_segment returned at its isascii() bail before either the #308 peel or the #271 surname site ran: the file's own docstring warns the absolute tests are structurally blind to a complexity regression, and for this stage the scaling test was blind too. A repeated 씨 walks the peel's site scan-back over the whole run -- every token is a post-nominal -- and measures 4.0-4.3 at base 800, inside the clean column the ASCII shapes already occupy, so _MAX_RATIO is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 3171267 commit 46fdc3e

2 files changed

Lines changed: 14 additions & 2 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ Add a dedicated `copy.deepcopy()` round-trip test for it too (see `test_regexes_
186186

187187
**`_normalize` must reach a fixed point** — storage and match-time share the one fold, and `Lexicon.__setstate__` re-validates, so a value that changes on re-normalization changes under its owner. `strip().strip(".")` alone is not idempotent (`'. a .'``' a '``'a'`). The loop is the fix; keep any new stripping inside it. **Anything built on `_normalize` must converge too**`_title_key` joins per-word `_normalize` and DROPS words that fold away; keeping the empty slot stored `'lt .'` as `'lt '`, a key match-time can never rebuild (so the entry is silently inert) and `__setstate__` rejects on the next round-trip as "not written by this version".
188188

189-
**Perf regressions are caught by the scaling test, not the absolute-time ones**`tests/v2/test_benchmark.py::test_parse_cost_grows_no_worse_than_linearly` times a repeated unit at n vs 4n over nine shapes (one per pipeline inner loop) and bounds the ratio; the `_thousand_names` tests use constant-size, delimiter-free input and are structurally blind to a complexity regression. Two rules when touching it: calibrate `_MAX_RATIO` against the WEAKEST quadratic's signal (a mixed quadratic surfaces far below the textbook 16×, so the operating point `_BASE` matters more than the bound), and confirm a planted regression fails it across REPEATED runs — one failure is a coin-flip on a timing test. The nine shapes cover different dimensions (segment count only via `commas`, intra-piece accumulation only via `particles`/`conjunctions`); measure before pruning one.
189+
**Perf regressions are caught by the scaling test, not the absolute-time ones**`tests/v2/test_benchmark.py::test_parse_cost_grows_no_worse_than_linearly` times a repeated unit at n vs 4n over ten shapes (one per pipeline inner loop) and bounds the ratio; the `_thousand_names` tests use constant-size, delimiter-free input and are structurally blind to a complexity regression. Two rules when touching it: calibrate `_MAX_RATIO` against the WEAKEST quadratic's signal (a mixed quadratic surfaces far below the textbook 16×, so the operating point `_BASE` matters more than the bound), and confirm a planted regression fails it across REPEATED runs — one failure is a coin-flip on a timing test. The ten shapes cover different dimensions (segment count only via `commas`, intra-piece accumulation only via `particles`/`conjunctions`, non-ASCII input only via `honorifics` — the other nine are pure ASCII, so `script_segment` returns at its bail and the CJK stages go unmeasured); measure before pruning one.
190190

191191
**Expected-failure tests use `@pytest.mark.xfail`** — the conftest parametrized fixture breaks `@unittest.expectedFailure`; always use `@pytest.mark.xfail` instead.
192192

tests/v2/test_benchmark.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,22 @@ def test_facade_thousand_names_under_a_second() -> None:
5050
# before pruning one that looks redundant:
5151
#
5252
# dimension covered by
53-
# token count all nine
53+
# token count all ten
5454
# piece count unmatched_*, plain_tokens, commas, titles
5555
# SEGMENT count commas ONLY -- deleting it leaves every
5656
# segment-keyed regression unguarded
5757
# intra-piece accumulation particles (one 799-token piece),
5858
# conjunctions (800) -- the merge() quadratic
5959
# masked-span count delimiter_pairs, quote_pairs (0 pieces:
6060
# everything is consumed as a delimited run)
61+
# NON-ASCII input honorifics ONLY -- every other unit here is
62+
# pure ASCII, so script_segment returns at
63+
# its isascii() bail and neither the #308
64+
# peel nor the #271 surname site is measured
65+
# at all. A repeated 씨 is what walks the
66+
# peel's site scan: every token is a
67+
# post-nominal, so the scan-back crosses the
68+
# whole run before declining
6169
_SHAPES = {
6270
"delimiter_pairs": "(a) ", # extract: matched pairs -> masked spans
6371
"quote_pairs": '"a" ', # extract: the open==close path
@@ -68,6 +76,7 @@ def test_facade_thousand_names_under_a_second() -> None:
6876
"titles": "Dr. ", # group: one long title chain
6977
"particles": "van ", # group: the prefix-chain inner loop
7078
"conjunctions": "and ", # group: merge() accumulating one piece
79+
"honorifics": "씨 ", # script_segment: the peel's site scan
7180
}
7281

7382
_BASE = 800
@@ -95,6 +104,9 @@ def test_facade_thousand_names_under_a_second() -> None:
95104
# a gap that is not there: at 800 the bound has ~1.4x over the worst
96105
# clean run (noise headroom on a shared runner) and ~1.3x under the
97106
# weakest quadratic. Re-derive BOTH numbers when adding a shape.
107+
# The tenth shape (honorifics) was measured into the clean column when
108+
# it arrived: 4.0-4.3 at base 800 across repeated runs, inside the range
109+
# the ASCII shapes already occupy, so neither number moved.
98110
_MAX_RATIO = 6.0
99111

100112

0 commit comments

Comments
 (0)