Skip to content

perf: P4 ネストif式(#19例5)が memoize でも 12–30s — GC/アロケ律速の実プロファイリング (#40 follow-up) #49

Description

@opaopa6969

背景

unlaxer-parser #40(packrat メモ化, 3.0.10)+ -Dtinyexpression.p4.memoize=true で、#19 の fraud 検知式のうち boolean/括弧曖昧形(#19 例1–4)は <0.5s に指数崩壊した。だが 深いネスト if 形(#19 例5)は memoize ON でも 12–30s と「数秒」に届かない。production では従来通り parse deadline(10s)→legacy fallback で安全に処理されるが、P4 経路に載せるには本式の根治が必要。

対象式(#5CalculatorImplTest.testCompilationFailedFunctions / P4PackratFraudFormulaTest):

if(not(isPresent($calculated_FirstAccessUserHash))){1}else{if($ForcedRelativeSuspiciousValue1){1}else{if($ForcedRelativeSuspiciousValue5){5}else{if($default_RelativeSuspiciousValue==5){5}else{if(($POST_PROCESS_OriginalSpec_CountryIsNotJapan>0.0)|($POST_PROCESS_OriginalSpec_BlackListOnOtherSites>0.0)|($POST_PROCESS_OriginalSpec_SuspiciousProvider>0.0)|($POST_PROCESS_OriginalSpec_OneUserAccessToMultiAccount>0.0)){5}else{$default_RelativeSuspiciousValue}}}}}

これまでに除外済みの仮説(推測×3 すべて律速ではない)

  1. (rule,position) の再導出PackratMemoTable.isSuccessMemoizable を全許可にした diagnostic(成功メモ化を全ルールに適用)でも P4 LSP: go-to-definition / find-references (ScopeStore 活用) #5 は ~12s 止まり。再パースは律速でない。
  2. success-memo の @backref 除外 — 上記 diagnostic で VariableRef(@backref) 含む部分木も成功メモ化したが効果なし。
  3. StringSource.peek 二重割当 — de-dup(unlaxer-parser PR feat(p4): packrat memoization ON by default — parity corpus fallback 3→0 #51, merged)しても P4 LSP: go-to-definition / find-references (ScopeStore 活用) #5 不変(むしろ計測 29s のことも)。

症状のシグネチャ → GC / アロケーション律速

parse 時間が 12s〜30s と実行ごとに大きくばらつく(同一入力・同一コード)。これは CPU 律速ではなく ヒープ/GC 圧(大量の短命オブジェクト割当) の典型。

調査方針(推測をやめ、実測する)

  1. プロファイラを当てる: async-profiler(alloc + cpu モード)か JFR で P4 LSP: go-to-definition / find-references (ScopeStore 活用) #5 単体 parse(memoize ON, deadline 無効化 tinyexpression.p4.parse.timeout.millis=0)の 割当フレーム上位CPU 上位を取る。-verbose:gc で GC 回数/停止時間も。
  2. 候補(プロファイル結果で裏取り):
    • StringSource.subSource/subString: 毎回 new String(codePoints,...) + source.codePoints().toArray()O(length) コピー。深いネストで peek/subSource が O(n) 回 × O(n) コピー = O(n²) の疑い。→ 根治は codePoints 配列を親と共有し [start,len) 窓で参照(コピー廃止)。大きめの core 改修。
    • ParseContext.trackCursorProgress / parseFrames push/pop / startParse/endParse: parser 呼び出し毎の割当・処理。
    • 診断系: farthestFailure* / trialHistory / maxReachedStackElements の毎回更新・リスト割当。
    • deadline listener の System.nanoTime() 毎回(CPU 小だが頻度大)。
  3. メモキーの取りこぼし確認: (parser, consumed, matched, tokenKind, invertMatch)matched が毎回変わるとなると同一 consumed でもキャッシュミス → 実質メモ無効の可能性。P4 LSP: go-to-definition / find-references (ScopeStore 活用) #5 で memo ヒット率を計測(デバッグカウンタ)。

受入条件

関連

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions