From 557dbfe647da0120b41e82ba6faeaf9e5d9cca73 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Wed, 1 Jul 2026 06:48:00 +0200 Subject: [PATCH] perf(strscan): re-pin go-ruby-strscan @main for O(n) anchored scans MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit go-ruby-strscan's anchored scan primitives (scan/skip/match?/check) were O(remaining length) per call: they matched by re.Match(str[pos..]) and then rejected a result that did not start at the cursor, so re.Match first scanned forward through the tail. A lexer that tries several alternative patterns per position paid that tail scan on every miss, making a full tokenize O(n^2) — the largest perf gap in the ecosystem (~80x slower than MRI, super-linear). The library now uses go-ruby-regexp's Regexp.MatchAt(s, pos), a cursor-anchored match (\G bound to pos, whole string visible for ^/\A/lookbehind) that tries a single match at the cursor: O(match length), not O(tail). Semantics stay byte-identical to MRI StringScanner (the differential-vs-MRI oracle passes). This binding routes through the fast path automatically: it delegates directly to sc.Scan/sc.Skip/sc.Match/sc.Check, so only the dependency pin changes. Re-pinned github.com/go-ruby-strscan/strscan @main (d0cc926); go-ruby-regexp bumps to fa3d6604 transitively for MatchAt. Full internal suite green, all 6 arches build. Measured rbgo-vs-MRI 4.0.5 tokenize (identifier lexer, alternatives-miss-scan tail), input x1/x2/x4/x8: before: 7.4x / 12.4x / 20.0x / 35.1x (ratio grows -> O(n^2)) after: 4.4x / 4.8x / 4.9x / 4.8x (ratio flat -> O(n)) At x8 the fix is 7.4x faster than before and the gap widens with input size; the residual ~4.8x is flat interpreter overhead, not the algorithmic bug. Co-Authored-By: Claude Opus 4.8 --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 302116c5..f0b44ba7 100644 --- a/go.mod +++ b/go.mod @@ -36,14 +36,14 @@ require ( github.com/go-ruby-prettyprint/prettyprint v0.0.0-20260629152429-60a380e82d7d github.com/go-ruby-prime/prime v0.0.0-20260629151744-aa26747e21a8 github.com/go-ruby-pstore/pstore v0.0.0-20260630081017-0dd55a12f94e - github.com/go-ruby-regexp/regexp v0.0.0-20260628133338-5a3a8b96feac + github.com/go-ruby-regexp/regexp v0.0.0-20260701043846-fa3d6604e1ee github.com/go-ruby-resolv/resolv v0.0.0-20260629153520-df410a5796ac github.com/go-ruby-rexml/rexml v0.0.0-20260629154021-5fb0f287ee8b github.com/go-ruby-scanf/scanf v0.0.0-20260629150220-414dbb31c386 github.com/go-ruby-securerandom/securerandom v0.0.0-20260630081933-3f81ff7d7fb0 github.com/go-ruby-set/set v0.0.0-20260630204346-ab6a8a92a910 github.com/go-ruby-shellwords/shellwords v0.0.0-20260629114104-e941e4210818 - github.com/go-ruby-strscan/strscan v0.0.0-20260630060240-10bd95a9c4a4 + github.com/go-ruby-strscan/strscan v0.0.0-20260701044334-d0cc926643a8 github.com/go-ruby-tsort/tsort v0.0.0-20260629151245-27c44f985c8b github.com/go-ruby-unicode-normalize/unicode-normalize v0.0.0-20260629152419-984d3fbcfb7f github.com/go-ruby-uri/uri v0.0.0-20260629113958-59633d1b0deb diff --git a/go.sum b/go.sum index cbd4e16a..599d0954 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/go-ruby-prime/prime v0.0.0-20260629151744-aa26747e21a8 h1:zghkWpebo93 github.com/go-ruby-prime/prime v0.0.0-20260629151744-aa26747e21a8/go.mod h1:dG/91hk8HlQ6YAdD64xiMFHTTmXjTWtapKWoG9JtcRA= github.com/go-ruby-pstore/pstore v0.0.0-20260630081017-0dd55a12f94e h1:JmbJL+0nq6HY1SX1tDBFQyo7M8JgbQ+ukwn8iLyNGfs= github.com/go-ruby-pstore/pstore v0.0.0-20260630081017-0dd55a12f94e/go.mod h1:tbYmHYXjnNXJCNYABDx9nWfuGBCXeKrp134bNsQeTpM= -github.com/go-ruby-regexp/regexp v0.0.0-20260628133338-5a3a8b96feac h1:jYaeh86ngvgRe2Kn4hUmSO+DV0okQv5PcdC+gMiyP2U= -github.com/go-ruby-regexp/regexp v0.0.0-20260628133338-5a3a8b96feac/go.mod h1:+0nK2X+aCPMO91bPMM/DT1OFUIzFQeoTPGkkOR3kd84= +github.com/go-ruby-regexp/regexp v0.0.0-20260701043846-fa3d6604e1ee h1:ctauEAdAq129WCrU+dndYcOjNlo1Y94vWNBtgXp1heE= +github.com/go-ruby-regexp/regexp v0.0.0-20260701043846-fa3d6604e1ee/go.mod h1:+0nK2X+aCPMO91bPMM/DT1OFUIzFQeoTPGkkOR3kd84= github.com/go-ruby-resolv/resolv v0.0.0-20260629153520-df410a5796ac h1:Y++tdsBF6lA/cw3c9uQ/ZyQZjP2N3wkA6pLF8a3HnHc= github.com/go-ruby-resolv/resolv v0.0.0-20260629153520-df410a5796ac/go.mod h1:RD3wPeMjMThQ7QVnB9O15gCllRg+RJhSEJu9DtFaLLQ= github.com/go-ruby-rexml/rexml v0.0.0-20260629154021-5fb0f287ee8b h1:hWNW3Z8g7Hwi17NJT912J0udGhK5dzxub8u7T07Oyv8= @@ -92,8 +92,8 @@ github.com/go-ruby-set/set v0.0.0-20260630204346-ab6a8a92a910 h1:Q41u365suJ1TlrX github.com/go-ruby-set/set v0.0.0-20260630204346-ab6a8a92a910/go.mod h1:F/P0PXeE5ybEPeWYfb1JAKWny1Li3dnlm6YxG2/vffE= github.com/go-ruby-shellwords/shellwords v0.0.0-20260629114104-e941e4210818 h1:z9b99kWJht8AgtlIbdiq7Y02j6eddxW+rr4MJpCS6kw= github.com/go-ruby-shellwords/shellwords v0.0.0-20260629114104-e941e4210818/go.mod h1:gJ+9ba+y8+DKenwjZKbTLVSuX6Xp7V5R7pZ6NwtD6x0= -github.com/go-ruby-strscan/strscan v0.0.0-20260630060240-10bd95a9c4a4 h1:SHtLFEJV/qaEhB4CuQxnRyMMVa+89nYwZqO8t2PjNrg= -github.com/go-ruby-strscan/strscan v0.0.0-20260630060240-10bd95a9c4a4/go.mod h1:Z50x1O7HpkWh6HvI2gvnbHrvqWsYTpz86IALiVfDSI4= +github.com/go-ruby-strscan/strscan v0.0.0-20260701044334-d0cc926643a8 h1:WxInOHCeP0EDmZqorooXIYTcDgBNm+3rOnfjRcWHlN0= +github.com/go-ruby-strscan/strscan v0.0.0-20260701044334-d0cc926643a8/go.mod h1:WNa2NtlvT8gaOrAw6m3VCb+8WgBWdOLLEWbKiugZ2wY= github.com/go-ruby-tsort/tsort v0.0.0-20260629151245-27c44f985c8b h1:qTw5CoNYJccf05o0QlyoYwaOik4RIrUI76HAR7RGqRw= github.com/go-ruby-tsort/tsort v0.0.0-20260629151245-27c44f985c8b/go.mod h1:QMZn01XgIYihSr1pAeqHtM5DNuf7q92rJzO1Uj1diwE= github.com/go-ruby-unicode-normalize/unicode-normalize v0.0.0-20260629152419-984d3fbcfb7f h1:OKU5SsaiAFX7U1u+KlW7c54zNYMnW5sRWoj4nOhY7yU=