Skip to content

refactor: search.ts の scan 系 IPC を bounded-concurrency parallel readFile 化 (#249)#250

Merged
ymnao merged 2 commits into
mainfrom
refactor/search-parallel-readfile-#249
Jun 27, 2026
Merged

refactor: search.ts の scan 系 IPC を bounded-concurrency parallel readFile 化 (#249)#250
ymnao merged 2 commits into
mainfrom
refactor/search-parallel-readfile-#249

Conversation

@ymnao

@ymnao ymnao commented Jun 27, 2026

Copy link
Copy Markdown
Owner

概要

electron/main/ipc/search.ts の 3 つの scan 系 IPC (全文検索 / 未解決 wikilink 走査 / バックリンクパネル refresh) の per-file await fsp.readFile() for ループを module-level 共有 pLimit(16) で並列化。N=tens〜low-thousands のワークスペースで wall-clock 5-20x 改善見込み (qualitative、I/O dominant)。

backlink panel refresh など user-facing な hot path の応答性向上が目的。

関連 Issue

closes #249

移行 Stage

(該当なし — 既存機能のリファクタリング、Stage 3 領域)

変更内容

  • electron/main/ipc/search.tssearchFilesImpl / scanUnresolvedWikilinksImpl / scanBacklinksImpl の per-file await fsp.readFile() for ループを Promise.all(... ioLimit(async () => {...})) で並列化
  • module-level const ioLimit = pLimit(16) を 3 関数で共有 (cross-IPC でも同時 fd 数を 16 に維持)
  • searchFilesImpl は事前 order 配列を廃止、最終 results.sort((filePath, lineNumber, matchStart)) で旧 sequential 実装の出力順序を再現
  • scanUnresolvedWikilinksImpl / scanBacklinksImpl は最終出力で既に sort 済 (pageName / sourceFile 昇順) のため追加 sort 不要
  • 各並列 task の冒頭 + readFile 後 (重い CPU 処理 = per-line scan / iterateWikilinkOccurrences の前) に if (isStale()) return; を配置 → cancel 反応性を旧 sequential 実装と同等以上に保つ
  • Promise.all 後にも if (isStale()) return []; を追加 (後段の sort / map 変換コストを skip)
  • package.json"p-limit": "^3.1.0" direct deps 追加 (既に transitive deps として 3.1.0 が存在、新規 download なし)
  • e2e mock (e2e/helpers/electron-api-mock.ts) は in-memory store.files[path] 経由で I/O 非該当 → 並列化対象外、symmetry 対応不要

動作確認

  • pnpm typecheck / pnpm typecheck:e2e pass
  • biome check clean (本 PR で touch した file のみ)
  • pnpm test 2230 pass / 2 skip (regression なし)
  • electron/main/ipc/search.test.ts 93 pass (cancel / cross-cancellation guard / 出力順序の test 全て pass)
  • CI (lint / typecheck / test / build / e2e / electron-e2e / dependency-review / CodeQL)

/code-review low + /simplify 結果

  • /code-review low: (none) (runtime-correctness bugs なし)
  • /simplify 1 回目 (commit 80717e0 前): 採用 1 件 (コメント簡素化)、見送り 9+ 件
  • /simplify 2 回目 (commit 80717e0 後): 採用 1 件 (Efficiency reviewer 指摘 = readFile 後の追加 isStale check、commit 5f04468 で対応)、残り見送りは 1 回目と同根拠

見送りした主な指摘 (両セッション共通)

指摘 見送り根拠
3 関数共通パターンを readMdFilesWithLimit helper に抽出 (Reuse / Simp / Alt) scope 拡大、3 関数の body が異なり薄い wrapper にしかならない。本 PR は parallel 化が主目的、helper 抽出は別 PR で検討
Promise.all 後の if (isStale()) return []; 削除 (Simp) defense in depth として残置、後段の sort / map 変換コストを skip する意義あり
SCAN_IO_CONCURRENCY constant 切り出し / electron/main/utils/io-limit.ts に export (Alt) scope 拡大、3 関数 caller が 1 file 内なので file-private で十分。将来他 scan IPC が必要になった時点で検討
results.sort() を bucket per-file 配列 + concat に置換 (Eff) 現状 O(N log N) sort で十分高速、ベンチ無しの先回り最適化は scope 外
io.map(...) の N Promise 一斉作成を worker pool で O(16) 化 (Eff) N=10k 規模で Promise 一斉作成コストは小 (~100KB 程度)、現状の bottleneck ではない
自前 semaphore で p-limit dep 削減 (Eff) proven library を選択、p-limit 3.1.0 は既に transitive deps として存在、direct 化のみで新規 download なし

スクリーンショット

UI 変更なし。

ymnao added 2 commits June 27, 2026 19:28
…ile 化 (#249)

- searchFilesImpl / scanUnresolvedWikilinksImpl / scanBacklinksImpl の per-file
  await fsp.readFile() for ループを module-level 共有 pLimit(16) で並列化
- backlink panel refresh / 全文検索 / 未解決リンク走査 (user-facing hot path) の
  wall-clock を 5-20x 改善見込み (N=tens〜low-thousands、I/O dominant)
- 複数 scan IPC が同時並行しても同時 fd 数を 16 に抑えるため module-level に
  ioLimit を 1 つ作って 3 関数で共有
- searchFilesImpl は事前 order 配列を廃し、最終 results.sort((filePath, lineNumber,
  matchStart)) で旧 sequential 実装の出力順序を再現
- scanUnresolvedWikilinksImpl / scanBacklinksImpl は元から最終 sort 済 (pageName /
  sourceFile 昇順) のため追加 sort 不要
- isStale check は各 task 開始時に 1 回 + Promise.all 後の最終 check に集約
  (旧 sequential の per-iter check と等価方針 + 後段の sort/map 変換コストを skip)
- e2e mock (e2e/helpers/electron-api-mock.ts) は in-memory store.files[path] 経由
  で I/O 非該当 → 並列化対象外、symmetry 対応不要
- p-limit は既に transitive deps として 3.1.0 が存在したため direct deps 化のみで
  新規 download なし
- 3 関数 (searchFilesImpl / scanUnresolvedWikilinksImpl / scanBacklinksImpl)
  の各並列 task で readFile の await 中に stale 化していた場合、後続の重い
  CPU 処理 (per-line scan / iterateWikilinkOccurrences の parse) を skip して
  cancel 反応性を上げる
- /simplify 2 回目 Efficiency reviewer 指摘 (cancel mid-batch で parse work が
  走り切る) への対応、1 行追加 × 3 箇所
@ymnao ymnao merged commit 43f6858 into main Jun 27, 2026
9 checks passed
@ymnao ymnao deleted the refactor/search-parallel-readfile-#249 branch June 27, 2026 12:19
Sign up for free to 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.

refactor: search.ts の sequential fsp.readFile を bounded-concurrency parallel 化

1 participant