Skip to content

Enable concurrent execution for parallel-safe matchers#158

Merged
Shougo merged 3 commits intomainfrom
copilot/add-matcher-concurrency-option
Mar 15, 2026
Merged

Enable concurrent execution for parallel-safe matchers#158
Shougo merged 3 commits intomainfrom
copilot/add-matcher-concurrency-option

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 13, 2026

Adds a matcherConcurrency option that allows matchers to run concurrently across item chunks, with automatic fallback to sequential execution when safety conditions aren't met. Default is 1 (fully backward compatible).

New option

matcherConcurrency: number (default: 1) — maximum number of concurrent chunks when running matchers.

Filter opt-in: static parallelSafe

BaseFilter gains a static parallelSafe = false class property. Filters declare themselves safe by overriding it:

export class Filter extends BaseFilter<Params> {
  static override parallelSafe = true;
  // processes items independently (e.g. highlight annotation only)
}

Parallel execution logic (#runMatchersConcurrently)

Parallel execution activates only when all hold:

  1. matcherConcurrency > 1
  2. Every active matcher has static parallelSafe = true
  3. Each chunk returns the same item count as it received (runtime safety check)

Items are split into ⌈N/concurrency⌉-sized chunks; callFilters runs on each via Promise.all, results are concatenated in order. Sorters always run sequentially on the full result.

Falls back transparently to sequential callFilters on: opt-out filter, item count mismatch, any exception, or stale runId (a newer concurrent run superseded this one).

Stale-result guard

#latestMatcherRunId is incremented per invocation. After Promise.all resolves, if the counter has advanced (a newer run started during the await), results are discarded and sequential fallback is used.

Files changed

  • types.tsmatcherConcurrency: number added to DduOptions
  • context.ts — default matcherConcurrency: 1; two new unit tests
  • base/filter.tsstatic parallelSafe = false on BaseFilter
  • ddu.ts#latestMatcherRunId field; #runMatchersConcurrently() method; #filterItems call site split into matchers (concurrent) + sorters (sequential)
  • doc/ddu.txtmatcherConcurrency option and parallelSafe attribute documented
Original prompt

目的

shougo/ddu.vim の matcher フェーズを安全に並列化できるように、core 側に並列化スキャフォールドを追加するPRを作成してください。今回の変更は並列化を強制的に有効化するものではなく、各 filter が自己申告する安全フラグ (static parallelSafe) を尊重した上で、チャンク単位で matchers を並列実行し、整合性検証(チャンク内でアイテムが削除されていないこと等)に失敗した場合は安全に逐次処理へフォールバックする仕組みを導入することが目的です。さらに、古い並列ジョブの結果適用を防ぐ runId/cancellation トークンを導入して入力変更時の競合を回避します。

要求仕様(実装要件)

  1. 新しいオプション
  • 名前: matcherConcurrency
  • 役割: matchers を並列に実行する際のワーカ数(上限)を指定する。デフォルトは 1(既存動作と互換)。
  • 位置: ddu の設定オプション構造に追加(既存のオプション定義ファイル/箇所に追加)。
  1. runMatchersConcurrently ヘルパの追加
  • ファイル: denops/ddu/ddu.ts(core のメイン実装ファイル)に追加
  • 動作:
    • callFilters(...) に相当する matcher フェーズの呼び出しをチャンク分割して並列実行する。具体的には:
      • concurrency = this.#options.matcherConcurrency(または options.matcherConcurrency)
      • matcherFilters が空か concurrency <= 1 の場合は従来の callFilters を呼ぶ
      • 全ての matcherFilters が parallelSafe を持つ (filter.constructor.parallelSafe === true) ことを確認する。そうでない場合は従来の callFilters を呼ぶ
      • items を chunkSize で分割し、各チャンクに対して callFilters を並列呼び出し(Promise.all)する
      • 各チャンクの結果配列の長さが入力チャンクと同じであることを検証。もし一致しない(filter がアイテムを削除/追加した)場合は安全策として逐次 callFilters を行いフォールバックする
      • 成功した場合はチャンク順に flatten して返す(元の順序保持)
    • runId / cancellation:
      • runMatchersConcurrently を呼ぶごとに内部カウンタ(this._latestMatcherRunId など)をインクリメントして現在の実行IDを記録
      • 各チャンク実行の Promise 解決時に結果をただちに適用する実装ではなく、呼び出し元が現在の runId と一致することを確認してから items を置き換える(古い実行結果の誤適用を防ぐ)。
    • エラーハンドリング:
      • どのチャンクで例外が発生しても逐次 path(従来 callFilters)へフォールバックする
    • 追加のログ/プロファイル(任意):
      • 各チャンクの実行時間・失敗をプロファイルログに出力できるようにする
  1. call site の差し替え
  • ddu のパイプライン中で現在 matchers を呼んでいる箇所(例: items = await callFilters(..., filters.matchers, ..., items))を runMatchersConcurrently 呼び出しに置き換える。
  • 置き換えは this コンテキストを尊重して行う(既存の loader/context/options を引き継ぐ)。
  1. 安全性と互換性
  • デフォルト設定 (matcherConcurrency: 1) では既存の逐次実行と完全互換
  • parallelSafe フラグがないフィルタ(既存の多く)については逐次実行にフォールバック
  • 並列化はオプトインの挙動にする(core 側で自動的に全ての filter を並列化しない)
  1. 型とランタイムチェック
  • TypeScript の型エラーが出ないこと。BaseFilter の型を変更する必要はなく、runtime では (ctor as any).parallelSafe をチェックする運用で良いが、型定義を拡張する patch を別途用意しても良い(今回は省略可)。
  1. テスト & ドキュメント
  • 単体テスト(Jest/deno のテストフレームワーク等)を追加:
    • 小さなケース: 小さな items 配列を与えて matcherConcurrency > 1 を設定したとき、結果が逐次実行と同じであることを検証
    • chunk のいずれかが結果長を変える(filter がアイテムを削除する)場合は逐次実行の結果と同じであること
    • runId による古��結果の破棄が正しく働くケース(モックで古い実行の遅延を再現)
  • README / docs に matcherConcurrency と parallelSafe の利用方法と注意点を追記

変更の範囲(実装対象ファイル/場所の提案)

  • denops/ddu/ddu.ts
    • runMatchersConcurrently 実装追加
    • matchers 呼び出し箇所を置換
    • this.#options に matcherConcurrency を参照する箇所を追加(オプション読み取り)
    • this._latestMatcherRunId(プライベートカウンタ)を追加
  • doc/ (プロジェクトのドキュメント) にオプションと parallelSafe の説明を追加
  • tests/ に並列化関連のユニットテストを追加

作業要領

  • feature ブランチ名: feature/matchers-concurrency
  • PR タイトル: "Enable concurrent execution for parallel-safe matchers"
  • PR 説明: 上記仕様の要約、動作の安全性(フォールバックと runId による古い結果破棄)、既定では無効化されることを明記
  • まずは最小変更で: runMatchersConcurrently + callsite 置換 + option の追加 + 単体テスト 1~2 件を含める。

テスト実行手順(レビューワ向け)

  1. 既存テストがあれば実行し、全てパスすること
  2. 追加テストを実行し、結果が既存の逐次実行と一致すること
  3. manual bench: matcherConcurrency を 1/2/4 にして代表ワークフローを N=5 回実行し、matcher の合計時間と全体リフレッシュ時間を比較

注意: リポジトリや他のコードベースの細かな構造(options の定義ファイルや callFilters の正確なシグネチャ)に依存するため、実装時に該当箇所の細かい調整を行い、CI と型チェックを通す必要があります。実装はまず feature/matchers-concurrency ブランチに最小パッチを作成し、CI で問題なければ拡張を続けてください。

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: 他の source の実装も確認しました。file_external はセットしていました。file_rec はセットしてなかったので追加しました。これで問題ないでしょう

Assistant: 良い対応です — file_rec にも status.time をセットしたなら、sorter_mtime 側で余分な Deno.stat が走るケースはほぼ無くなるはずです。短く確認事項と念のためのチェックリスト、次にやると良いことをまとめます。

最低限の確認(すぐやる)

  • 動作確認(手動)
    • file / file_rec / file_external 等の代表的なソースで表示して、期待どおりにソート(mtime 順)されることを目視で確認。
    • matcher の入力でハイライトや表示が変わる箇所をチェック(先に直したキャッシュ周りの挙動も確認)。
  • 型/lint
    • deno check / tsc(プロジェクトに合わせて)を走らせて型エラーが出ないことを確認。

性能確認(ベンチ)

  • N=5〜10 の反復で以下を記録:
    • total refresh time(median)
    • sorter time(median)
    • converterCache.stats()(hits/misses/evicted/size)
    • RSS(メモリ)
  • 比較対象:変更前(もし記録があれば)と変更後。this.#options.profile = true にしてログを取ると楽です。

念のためチェックするエッジケース

  • path/action.path が空のソース(grep など multiline ソース) — それらは stat を必要とする場合があるか確認。
  • ネットワークや remote path を扱うソース(存在するなら stat に時間が掛かる可能性)。
  • converter が item を mutate してキャッシュキーを変えてしまうケース(key の安定性を再確認)。

改善案(任意)

  • profile 時に自動で converterCache.stats() を毎回ログに出す小パッチ(デバッグが楽になります)。作りますか?
  • diff/merge のユニットテストを追加して、ハイライトの重複や優先順の回帰を防ぐ。必要ならテストコードを作ります。
  • 将来的に「cache の型を厳密化(ジェネリック)」して any を完全に排除するパッチも出せます。

まとめ

  • file_rec に status.time を入れた対応で問題ない見込みです。上の最低限の確認(手動動作 + 型チェック + ベンチ)をやって問題がなければクローズで大丈夫です。
  • 必要なら私のほうで次のどれかを作ります:profile 自動ログの小パッチ/ユニットテスト/型改善パッチ。どれを作りましょうか?(短く回答ください)

User: >並列化・制御付き並行処理(高効果だが慎重に)

なぜ: CPU を有効活用できれば処理時間が短縮するが副作用に注意。
改善案:
converters/matchers を chunk に分けて並列実行(Promise.all with concurrency limit)。

やってみたいけれど危険性もありますね。これは専用ブランチを切って慎重にいきたいところ。

レンダリング最適化(virtualization / lazy apply)(UI 側)

なぜ: 大量アイテムを一度に描画するのは時間がかかる。
改善案:
画面上に表示される範囲のみを優先して加工(前後の少数行のみ計算)、残りは background で逐次処理。
display count の上限を設ける、または初回は簡易描画 → 後か...

This pull request was created from Copilot chat.


📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

…her execution

Co-authored-by: Shougo <41495+Shougo@users.noreply.github.com>
Copilot AI changed the title [WIP] Add matcherConcurrency option for parallel execution of matchers Enable concurrent execution for parallel-safe matchers Mar 13, 2026
Copilot AI requested a review from Shougo March 13, 2026 11:22
@Shougo Shougo marked this pull request as ready for review March 14, 2026 06:14
@Shougo Shougo merged commit adabde7 into main Mar 15, 2026
3 checks passed
@Shougo Shougo deleted the copilot/add-matcher-concurrency-option branch March 15, 2026 01:24
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.

2 participants