Grep機能拡張
1. 概要
サクラエディタのGrep機能に対し、以下2つの拡張を実装する。
- Grep処理(bregonig.dll)のマルチスレッド化 — ファイル内検索を並列実行し、大量ファイルの検索を高速化する
- 除外ファイル指定の正規表現対応 —正規表現によるファイル除外を可能にする
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.cpp の ShareData_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) を確認
RunBatch は bool を返し、呼び出し元で 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
Grep機能拡張
1. 概要
サクラエディタのGrep機能に対し、以下2つの拡張を実装する。
2. 機能仕様
2.1 マルチスレッドGrep
2.1.1 対象
通常Grep(
bGrepReplace=false)のみ。Grep置換は副作用(ファイル上書き)を伴うため従来の直列処理を維持する。2.1.2 処理フロー
サブフォルダー単位でバッチを分割することで、1バッチ分のみメモリに保持し、サブフォルダー順(A→B→C)の出力順序を保証する。
2.1.3 スレッド数
nGrepThreadCount(1〜8にクランプ)と論理コア数 / 4の大きい方を採用hardware_concurrency() / 4に上限なし2.1.4 ini設定
[Common]nGrepThreadCount2.1.5 スレッドプール同期機構
poolBatchIdsize_tbPoolShutdownbooltrueでワーカーに終了を指示pPoolBatchconst vector<SGrepFileTask>*poolNextTaskatomic<size_t>poolBatchActiveatomic<int>bWorkCancelledatomic<bool>cvPoolStartcondition_variable2.1.6 排他制御
atomic<size_t> poolNextTask(ロックフリー)atomic<int> atomicHitCountsharedMessage)std::mutex resultMutex+std::lock_guardstd::set<std::wstring>をresultMutexで保護2.1.7 CBregexpスレッド安全性
CBregexpは内部状態を持つためスレッドセーフではない。各ワーカースレッドはスレッド生存期間中に1回だけ独自インスタンスを生成・コンパイルし、スレッド間で共有しない。2.1.8 キャンセル処理
BlockingHook()/IsCanceled()で検出しbWorkCancelled.store(true, release)bWorkCancelled.load(acquire)を確認RunBatchはboolを返し、呼び出し元でnGrepTreeResult = -1を設定(一元管理)2.1.9 例外安全性
ワーカーラムダは
try/catch(...)で囲み、poolBatchActive.fetch_sub(1)をcatchブロックの外に配置。正常終了・例外発生どちらの経路でも必ずデクリメントされ、デッドロックを防止する。2.1.10 出力順序
2.2 除外ファイル指定の正規表現対応
2.2.1 構文
Grepダイアログのファイルパターン欄で 正規表現パターンとして解釈させる。
*.cpp##.git.*\.bak$2.2.2 マッチング対象
ファイルのフルパス全体に対して正規表現マッチングを行う。
2.2.3 大文字小文字
大文字小文字を区別する(
CBregexp::optCaseSensitive)。大文字小文字を無視したい場合は、正規表現内で(?i)を使用する。2.2.4 入力バリデーション
Grep実行前にメインスレッドで全パターンの
InitRegexp()+Compile()を事前検証する。無効なパターンはエラーダイアログを表示してGrep実行を中止する。2.2.5 適用範囲
通常Grep・Grep置換の両方で有効。
DoGrepTree()ファイルループ内2.2.6 ヘッダー表示
Grep結果ウィンドウの「除外ファイル」行に表示する。
3. アーキテクチャ
3.1 クラス・関数構成
3.2 新規追加した構造体・関数
SGrepFileTaskDoGrepTreeEnumerate()vecTasksにファイル情報を収集(メインスレッド用)DoGrepFileWorker()3.3 DoGrepTree の正規表現除外最適化
DoGrepTree()にstd::vector<CBregexp>* pExclRegexps = nullptr引数を追加。最上位呼び出しのみ関数内でコンパイルし、再帰呼び出しにはコンパイル済みポインタを引き継ぐ。4. 変更ファイル一覧
4.1 主要改修ファイル
sakura_core/grep/m_vecExceptFileRegexPatterns追加、!パーサー、GetExcludeFiles()、ClearItems()sakura_core/agent/SGrepFileTask構造体、DoGrepTreeEnumerate/DoGrepFileWorker宣言、DoGrepTree引数追加sakura_core/agent/DoGrepTree正規表現除外、DoGrepTreeEnumerate/DoGrepFileWorker実装sakura_core/env/m_nGrepThreadCountメンバー追加sakura_core/env/m_nGrepThreadCountデフォルト値2sakura_core/env/nGrepThreadCountini読み書き([Common]セクション)4.2 ビルドエラー修正
std::size(member)→std::extent_v<decltype(member)>への置き換え(11箇所)。C++20準拠のMSVCで非静的メンバ変数へのstd::size()が定数式として評価できない問題を修正。sakura_core/dlg/sakura_core/dlg/sakura_core/dlg/sakura_core/dlg/sakura_core/dlg/sakura_core/outline/sakura_core/env/sakura_core/typeprop/sakura_core/types/5. 制約・注意事項
#プレフィックスは従来のワイルドカード方式を維持InitRegexp(nullptr, ..., false)でUI表示なしにDLLをロードstd::filesystemFindFirstFile/FindNextFileベース)ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
grep時間の処理短縮の為、修正し使用してます。
除外ファイルは正規表現指定にして特定フォルダに絞りる為などなどしたかったのでしてます。
自分用と思い作成したので画面など変更してません。
可能な限り取り込んで頂きリリースして頂ければと思います。
両機能(4スレッド)で4分の1程度の処理時間になりました。
agent.zip