Skip to content

Grep機能拡張 #2439

@hpmy-dev

Description

@hpmy-dev

Grep機能拡張

1. 概要

サクラエディタのGrep機能に対し、以下2つの拡張を実装する。

  1. Grep処理(bregonig.dll)のマルチスレッド化 — ファイル内検索を並列実行し、大量ファイルの検索を高速化する
  2. 除外ファイル指定の正規表現対応 —正規表現によるファイル除外を可能にする

2. 機能仕様

2.1 マルチスレッドGrep

2.1.1 対象

通常Grep(bGrepReplace=false)のみ。Grep置換は副作用(ファイル上書き)を伴うため従来の直列処理を維持する。

2.1.2 処理フロー

DoGrep()
  ├─ スレッドプール生成(1回のみ・nThreads個)
  │    各ワーカー: CBregexp/CSearchStringPattern をスレッドローカルに1回だけ初期化
  │
  ├─ バッチ0: 検索パス直下ファイル → DoGrepTreeEnumerate(bSubFolder=false) → RunBatch
  ├─ バッチ1: サブフォルダーA を再帰列挙 → RunBatch → vecTasks解放
  ├─ バッチ2: サブフォルダーB を再帰列挙 → RunBatch → vecTasks解放
  │   ...(1バッチ分のみメモリに保持、A→B→C の出力順序を保証)
  │
  └─ スレッドプール shutdown → join

サブフォルダー単位でバッチを分割することで、1バッチ分のみメモリに保持し、サブフォルダー順(A→B→C)の出力順序を保証する。

2.1.3 スレッド数

const int nIniThreads = GetDllShareData().m_Common.m_sSearch.m_nGrepThreadCount;
const unsigned int nClampedIni = (unsigned int)std::max(1, std::min(nIniThreads, 8));
const unsigned int nThreads = std::max<unsigned int>(nClampedIni, hardware_concurrency() / 4);
  • iniファイルの nGrepThreadCount(1〜8にクランプ)と 論理コア数 / 4 の大きい方を採用
  • デフォルト値: 2(iniファイル未設定時)
  • 上限: iniファイル設定値は8まで、hardware_concurrency() / 4 に上限なし

2.1.4 ini設定

[Common]
nGrepThreadCount=2
項目
セクション [Common]
キー nGrepThreadCount
int
範囲 1〜8(範囲外は自動クランプ)
デフォルト 2

注意: CShareData_IO.cppShareData_IO_Common() 関数内で pszSecName = L"Common" として common.m_sSearch.m_nGrepThreadCount を読み書きしているため、iniファイル上のセクションは [Common] です。

2.1.5 スレッドプール同期機構

変数 役割
poolBatchId size_t バッチ番号。インクリメントで新バッチを通知
bPoolShutdown bool true でワーカーに終了を指示
pPoolBatch const vector<SGrepFileTask>* 現在バッチのタスクリスト
poolNextTask atomic<size_t> 次に処理するタスクのインデックス(ロックフリー分配)
poolBatchActive atomic<int> 現在バッチを処理中のワーカー数
bWorkCancelled atomic<bool> キャンセル済みフラグ(バッチ間で維持)
cvPoolStart condition_variable バッチ開始 / シャットダウン通知

2.1.6 排他制御

リソース 保護方法
ファイルタスク配布 atomic<size_t> poolNextTask(ロックフリー)
ヒット数 atomic<int> atomicHitCount
結果バッファ (sharedMessage) std::mutex resultMutex + std::lock_guard
フォルダーヘッダー重複排除 std::set<std::wstring>resultMutex で保護
AddTail() / BlockingHook() / IsCanceled() メインスレッドからのみ呼び出し

2.1.7 CBregexpスレッド安全性

CBregexp は内部状態を持つためスレッドセーフではない。各ワーカースレッドはスレッド生存期間中に1回だけ独自インスタンスを生成・コンパイルし、スレッド間で共有しない。

// ワーカースレッド起動時(1回のみ)
CBregexp localRegexp;
CSearchStringPattern localPattern;
localPattern.SetPattern(nullptr, searchKey.c_str(), searchKey.size(),
                        sSearchOption, &localRegexp);

2.1.8 キャンセル処理

  • メインスレッド: BlockingHook() / IsCanceled() で検出し bWorkCancelled.store(true, release)
  • ワーカースレッド: 32行ごとに bWorkCancelled.load(acquire) を確認
  • RunBatchbool を返し、呼び出し元で nGrepTreeResult = -1 を設定(一元管理)

2.1.9 例外安全性

ワーカーラムダは try/catch(...) で囲み、poolBatchActive.fetch_sub(1)catch ブロックの外に配置。正常終了・例外発生どちらの経路でも必ずデクリメントされ、デッドロックを防止する。

2.1.10 出力順序

  • サブフォルダー間: 保証される(A→B→C の順にバッチ処理)
  • サブフォルダー内のファイル間: 実行のたびに変わる場合がある(並列処理のため)

2.2 除外ファイル指定の正規表現対応

2.2.1 構文

Grepダイアログのファイルパターン欄で 正規表現パターンとして解釈させる。

*.cpp;*.h;.*\\bin\\[^.]+$;.*\.bak$
プレフィックス 動作
なし ワイルドカード検索対象(従来機能) *.cpp
# ワイルドカードフォルダー除外(従来機能) #.git
なし 正規表現ファイル除外(新機能) .*\.bak$

2.2.2 マッチング対象

ファイルのフルパス全体に対して正規表現マッチングを行う。

パターン: .*\\obj\\.*
マッチ対象: C:\project\src\obj\debug\file.obj  → 除外
マッチ対象: C:\project\src\main.cpp            → 対象

2.2.3 大文字小文字

大文字小文字を区別する(CBregexp::optCaseSensitive)。大文字小文字を無視したい場合は、正規表現内で (?i) を使用する。

2.2.4 入力バリデーション

Grep実行前にメインスレッドで全パターンの InitRegexp() + Compile() を事前検証する。無効なパターンはエラーダイアログを表示してGrep実行を中止する。

2.2.5 適用範囲

通常Grep・Grep置換の両方で有効。

処理 除外判定箇所
通常Grep(並列) ワーカースレッド内(スレッドローカルCBregexp使用)
Grep置換(直列) DoGrepTree() ファイルループ内

2.2.6 ヘッダー表示

Grep結果ウィンドウの「除外ファイル」行に表示する。

除外ファイル   *.cpp;*.h;.*\\bin\\[^.]+$;.*\.bak$

3. アーキテクチャ

3.1 クラス・関数構成

CGrepAgent::DoGrep()
├─ [hWndTarget]     → DoGrepFile()(ウィンドウGrep・既存)
├─ [bGrepReplace]   → DoGrepTree() → DoGrepReplaceFile()(直列・既存)
└─ [通常Grep]       → 並列処理ブロック(新規)
    ├─ スレッドプール生成
    ├─ RunBatch() ラムダ
    │   ├─ ワーカー起床(condition_variable)
    │   ├─ メインスレッドUI監視ループ
    │   └─ 結果フラッシュ
    ├─ バッチ0: DoGrepTreeEnumerate(bSubFolder=false) → RunBatch
    ├─ バッチ1..N: CGrepEnumFilterFolders → DoGrepTreeEnumerate → RunBatch
    └─ スレッドプール shutdown

3.2 新規追加した構造体・関数

名前 種別 説明
SGrepFileTask 構造体 並列処理で使用するファイル情報(fullPath, fileName, baseFolder, folder, relPath)
DoGrepTreeEnumerate() メンバー関数 フォルダー走査のみ行い vecTasks にファイル情報を収集(メインスレッド用)
DoGrepFileWorker() メンバー関数 スレッドセーフなファイル内検索(UI更新なし・atomic cancelフラグ使用)

3.3 DoGrepTree の正規表現除外最適化

DoGrepTree()std::vector<CBregexp>* pExclRegexps = nullptr 引数を追加。最上位呼び出しのみ関数内でコンパイルし、再帰呼び出しにはコンパイル済みポインタを引き継ぐ。


4. 変更ファイル一覧

4.1 主要改修ファイル

ファイル パス 変更内容
CGrepEnumKeys.h sakura_core/grep/ m_vecExceptFileRegexPatterns 追加、!パーサー、GetExcludeFiles()ClearItems()
CGrepAgent.h sakura_core/agent/ SGrepFileTask 構造体、DoGrepTreeEnumerate/DoGrepFileWorker 宣言、DoGrepTree 引数追加
CGrepAgent.cpp sakura_core/agent/ 並列処理ブロック全体(スレッドプール+バッチ処理)、DoGrepTree 正規表現除外、DoGrepTreeEnumerate/DoGrepFileWorker 実装
CommonSetting.h sakura_core/env/ m_nGrepThreadCount メンバー追加
CShareData.cpp sakura_core/env/ m_nGrepThreadCount デフォルト値 2
CShareData_IO.cpp sakura_core/env/ nGrepThreadCount ini読み書き([Common]セクション)

4.2 ビルドエラー修正

std::size(member)std::extent_v<decltype(member)> への置き換え(11箇所)。C++20準拠のMSVCで非静的メンバ変数への std::size() が定数式として評価できない問題を修正。

ファイル パス
CDlgCompare.cpp sakura_core/dlg/
CDlgDiff.cpp sakura_core/dlg/
CDlgFavorite.cpp sakura_core/dlg/
CDlgTagJumpList.cpp sakura_core/dlg/
CDlgWindowList.cpp sakura_core/dlg/
CDlgFuncList.cpp sakura_core/outline/
CShareData_IO.cpp sakura_core/env/
CDlgTypeList.cpp sakura_core/typeprop/
CType_Erlang.cpp sakura_core/types/

5. 制約・注意事項

項目 内容
Grep置換 直列処理を維持(データ競合防止)
フォルダー内出力順序 並列処理のため不定(サブフォルダー間の順序は保証)
フォルダー除外 # プレフィックスは従来のワイルドカード方式を維持
正規表現DLL ワーカースレッドでは InitRegexp(nullptr, ..., false) でUI表示なしにDLLをロード
std::filesystem 本改修では使用しない(ファイル走査はWin32 API FindFirstFile/FindNextFile ベース)

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
grep時間の処理短縮の為、修正し使用してます。
除外ファイルは正規表現指定にして特定フォルダに絞りる為などなどしたかったのでしてます。
自分用と思い作成したので画面など変更してません。

可能な限り取り込んで頂きリリースして頂ければと思います。
両機能(4スレッド)で4分の1程度の処理時間になりました。

agent.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions