') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); perf(p4): AST キャッシュ + 文法網羅/パリティテスト + 不安定性調査 by opaopa6969 · Pull Request #26 · opaopa6969/tinyexpression · GitHub
Skip to content

perf(p4): AST キャッシュ + 文法網羅/パリティテスト + 不安定性調査 - #26

Merged
opaopa6969 merged 5 commits into
masterfrom
p4-perf-and-tests
Aug 27, 2026
Merged

perf(p4): AST キャッシュ + 文法網羅/パリティテスト + 不安定性調査#26
opaopa6969 merged 5 commits into
masterfrom
p4-perf-and-tests

Conversation

@opaopa6969

Copy link
Copy Markdown
Owner

現 master の P4 実装の上に性能改善とテストを追加。詳細: docs/findings-2026-06-15-unlaxer-3.0.4-and-p4.md

主な変更

  • AST キャッシュ (AstEvaluatorCalculator.cachedTypedAst): apply ごとの再パースを回避。12KB式の繰り返し評価 5717ms→0.19ms。javacode は ~4KB 超でコンパイル不能(64KBメソッド上限)のため巨大式はインタプリタが唯一解。
  • テスト追加: GrammarCoverageInterpreterTest(文法網羅・高速インタプリタ), InterpreterJavacodeParityTest(共通サブセットで interpreter==javacode 証明), KnownP4BugsTest(@ignore・発見バグ再現), BackendSpeedBenchmarkTest(@ignore・ベンチ)。
  • pom: -Dtinyexpression.skipRailroad 追加。forkCount 並列は不採用(下記 §6 のテスト隔離問題)。

発見したバグ(issue 化済み)

テスト不安定性の調査

JavaCodeBlockPolicy のグローバル静的状態の実行順依存が主因(findings §6)。本PRは clean master 比で新規失敗ゼロ(検証済み範囲)。ローカルでは master 自体が一部環境要因で赤いため、CI での緑判定を依頼します。

🤖 Generated with Claude Code

opaopa6969and others added 2 commits June 15, 2026 19:48
…ity tests
AstEvaluatorCalculator re-parsed and re-mapped the formula on every apply(). For large
formulas (tens of KB) re-parsing dominated cost (~5.7s/eval at 12KB). Cache the typed
P4 AST for declaration-free formulas the typed path has already evaluated and reuse it
across apply() calls (structure only — context still varies). Repeated evaluation of a
12KB formula drops ~5717ms -> ~0.19ms; javacode cannot compile such formulas at all
(64KB method limit), so the interpreter is the only viable path for large expressions.
Tests (run on the fast interpreter):
- GrammarCoverageInterpreterTest: grammar-derived expressions and their runtime answers
(arithmetic, math fns, boolean precedence OR<AND<XOR, not nesting via if(...), control
flow, match, strings, variables, cache freshness).
- InterpreterJavacodeParityTest: proves interpreter == javacode on the common subset.
- KnownP4BugsTest (@ignore): reproductions of P4 interpreter bugs found while writing the
above — standalone not(...) always false (#25), variadic min/max & boolean precedence
overridden by cross-check (#21), math-function arithmetic (unlaxer-parser#43).
- BackendSpeedBenchmarkTest (@ignore): the perf benchmark.
pom: add -Dtinyexpression.skipRailroad to skip documentation-only SVG generation during
dev/test. surefire forkCount parallelism was tried but NOT enabled — several existing
tests share process-wide static state (JavaCodeBlockPolicy, Parser.get cache) and fail
under parallel forks; see docs/findings §6.
Findings, bugs and the test-instability root-cause analysis:
docs/findings-2026-06-15-unlaxer-3.0.4-and-p4.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@opaopa6969
opaopa6969 merged commit 39e242c into masterAug 27, 2026
2 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@opaopa6969