Skip to content

Commit f7d422d

Browse files
authored
Merge pull request DeusData#1528 from DeusData/fix/pylsp-scale-detector-calibration
test(scale): calibrate the py-LSP quadratic detector to detect quadratic (DeusData#1527)
2 parents 367ba25 + 0cdcf9a commit f7d422d

1 file changed

Lines changed: 45 additions & 25 deletions

File tree

‎tests/test_py_lsp_scale.c‎

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,29 @@ static char *build_fixture(int n_classes, int *out_len) {
2121
* 256 covers up to 9-digit indices comfortably. */
2222
int approx = n_classes * 256 + 1024;
2323
char *buf = (char *)malloc((size_t)approx);
24-
if (!buf) return NULL;
24+
if (!buf)
25+
return NULL;
2526
int pos = 0;
26-
pos += snprintf(buf + pos, (size_t)(approx - pos),
27-
"from typing import Self\n");
27+
pos += snprintf(buf + pos, (size_t)(approx - pos), "from typing import Self\n");
2828
for (int i = 0; i < n_classes; i++) {
2929
int n = snprintf(buf + pos, (size_t)(approx - pos),
30-
"class Cls%d:\n"
31-
" def method(self) -> int:\n"
32-
" return %d\n"
33-
" def chain(self) -> Self:\n"
34-
" return self\n", i, i);
35-
if (n < 0 || pos + n >= approx) break;
30+
"class Cls%d:\n"
31+
" def method(self) -> int:\n"
32+
" return %d\n"
33+
" def chain(self) -> Self:\n"
34+
" return self\n",
35+
i, i);
36+
if (n < 0 || pos + n >= approx)
37+
break;
3638
pos += n;
3739
}
3840
int n = snprintf(buf + pos, (size_t)(approx - pos), "def use():\n");
3941
pos += n;
4042
for (int i = 0; i < n_classes; i++) {
4143
int m = snprintf(buf + pos, (size_t)(approx - pos),
42-
" Cls%d().chain().chain().method()\n", i);
43-
if (m < 0 || pos + m >= approx) break;
44+
" Cls%d().chain().chain().method()\n", i);
45+
if (m < 0 || pos + m >= approx)
46+
break;
4447
pos += m;
4548
}
4649
*out_len = pos;
@@ -50,16 +53,20 @@ static char *build_fixture(int n_classes, int *out_len) {
5053
static double measure(int n_classes, int *out_calls, int *out_resolved) {
5154
int slen = 0;
5255
char *src = build_fixture(n_classes, &slen);
53-
if (!src) return -1.0;
56+
if (!src)
57+
return -1.0;
5458
struct timespec t0, t1;
5559
clock_gettime(CLOCK_MONOTONIC, &t0);
56-
CBMFileResult *r = cbm_extract_file(src, slen, CBM_LANG_PYTHON,
57-
"test", "scale.py", 0, NULL, NULL);
60+
CBMFileResult *r =
61+
cbm_extract_file(src, slen, CBM_LANG_PYTHON, "test", "scale.py", 0, NULL, NULL);
5862
clock_gettime(CLOCK_MONOTONIC, &t1);
5963
double ms = elapsed_ms(t0, t1);
60-
if (out_calls) *out_calls = r ? r->calls.count : 0;
61-
if (out_resolved) *out_resolved = r ? r->resolved_calls.count : 0;
62-
if (r) cbm_free_result(r);
64+
if (out_calls)
65+
*out_calls = r ? r->calls.count : 0;
66+
if (out_resolved)
67+
*out_resolved = r ? r->resolved_calls.count : 0;
68+
if (r)
69+
cbm_free_result(r);
6370
free(src);
6471
return ms;
6572
}
@@ -71,22 +78,35 @@ TEST(pylsp_scale_linear_growth) {
7178
double t100 = measure(100, &c100, &r100);
7279
double t500 = measure(500, &c500, &r500);
7380
double t2000 = measure(2000, &c2000, &r2000);
74-
printf(" scale: 100=%.1fms (calls=%d resolved=%d) 500=%.1fms (calls=%d resolved=%d) 2000=%.1fms (calls=%d resolved=%d)\n",
75-
t100, c100, r100, t500, c500, r500, t2000, c2000, r2000);
81+
printf(" scale: 100=%.1fms (calls=%d resolved=%d) 500=%.1fms (calls=%d resolved=%d) "
82+
"2000=%.1fms (calls=%d resolved=%d)\n",
83+
t100, c100, r100, t500, c500, r500, t2000, c2000, r2000);
7684

7785
/* Sanity: each scale produces roughly the same resolution ratio. */
7886
double r_pct_100 = c100 ? (double)r100 / c100 : 0.0;
7987
double r_pct_2000 = c2000 ? (double)r2000 / c2000 : 0.0;
8088
ASSERT(r_pct_100 > 0.5);
8189
ASSERT(r_pct_2000 > 0.5);
8290

83-
/* Linear growth check: 20x input should be at most ~30x time
84-
* (allowing constant-factor overhead). 20x with quadratic would be
85-
* 400x — easy to detect. */
86-
if (t100 > 0.5) { // skip when t100 too small to compare reliably
91+
/* Quadratic-growth detector. 20x input: linear ~20-30x time, clear
92+
* quadratic ~400x. The bound sits at 200x — mid-way in log space — so a
93+
* real quadratic regression still fails by 2x while the CURRENT, KNOWN
94+
* superlinear resolve curve does not produce false release blocks.
95+
*
96+
* The old bound of 100x was calibrated as "linear plus generous overhead",
97+
* but the resolve path has never been linear here: measured 2026-08-11
98+
* (sanitized builds, deterministic across runs) — quiet arm64 host
99+
* 57-68x, macos-15-intel CI runner 101.1x, per-function cost growing
100+
* 0.5ms -> 1.7ms from 100 to 2000 functions. That ~O(n^1.4-1.5) curve is
101+
* the audited short-name-lookup/negative-memo gap, tracked as #1527; this
102+
* detector was flagging host CONSTANTS, not a complexity change. When
103+
* #1527 lands, tighten this back down (~40x holds linear honestly). */
104+
if (t100 > 0.5) { // skip when t100 too small to compare reliably
87105
double ratio = t2000 / t100;
88-
printf(" scale ratio 2000/100: %.1fx (linear ~20x, quadratic ~400x)\n", ratio);
89-
ASSERT(ratio < 100.0); // generous bound; flags clear quadratic
106+
printf(" scale ratio 2000/100: %.1fx (linear ~20x, known-superlinear ~60-100x, "
107+
"quadratic ~400x)\n",
108+
ratio);
109+
ASSERT(ratio < 200.0); // flags clear quadratic; #1527 tracks the curve itself
90110
}
91111
PASS();
92112
}

0 commit comments

Comments
 (0)