4343#include < set>
4444#include < functional>
4545#include < string_view>
46+ #ifdef _DEBUG
47+ #include < chrono>
48+ #endif
4649
4750constexpr DWORD UICHECK_INTERVAL_MILLISEC = 100 ; // UI確認の時間間隔
4851constexpr DWORD ADDTAIL_INTERVAL_MILLISEC = 50 ; // 結果出力の時間間隔
@@ -175,6 +178,9 @@ class CFileLoadOrWnd{
175178 }
176179 int GetPercent (){
177180 if ( m_hWnd ){
181+ if ( m_nLineNum <= 0 ){
182+ return 0 ; // 行数 0 のウインドウでのゼロ除算を防ぐ
183+ }
178184 return (int )(m_nLineCurrent * 100.0 / m_nLineNum);
179185 }
180186 return m_cfl.GetPercent ();
@@ -286,9 +292,12 @@ static int GetHwndTitle(HWND& hWndTarget, CNativeW* pmemTitle, WCHAR* pszWindowN
286292 if ( 0 != wcsncmp (pszFile, szTargetPrefix, cchTargetPrefix) ){
287293 return 0 ; // ハンドルGrepではない
288294 }
289- if ( 0 >= ::swscanf_s (pszFile + cchTargetPrefix, L" %zx" , (size_t *)&hWndTarget) || !IsSakuraMainWindow (hWndTarget) ){
295+ // HWND* を size_t* として読み書きする型パンニングを避けるため、一旦 size_t で受けてから変換する
296+ size_t nHwndValue = 0 ;
297+ if ( 0 >= ::swscanf_s (pszFile + cchTargetPrefix, L" %zx" , &nHwndValue) || !IsSakuraMainWindow ((HWND )nHwndValue) ){
290298 return -1 ; // ハンドルを読み取れなかった、または、対象ウインドウハンドルが存在しない
291299 }
300+ hWndTarget = (HWND )nHwndValue;
292301 if ( pmemTitle ){
293302 const wchar_t * p = L" Window:[" ;
294303 pmemTitle->SetStringHoldBuffer (p, 8 );
@@ -301,6 +310,9 @@ static int GetHwndTitle(HWND& hWndTarget, CNativeW* pmemTitle, WCHAR* pszWindowN
301310 WCHAR szGrep[100 ];
302311 editInfo->m_bIsModified = false ;
303312 const EditNode* node = CAppNodeManager::getInstance ()->GetEditNode (hWndTarget);
313+ if ( !node ){
314+ return -1 ; // 管理リストに見つからない(対象ウインドウが閉じられた直後など)
315+ }
304316 WCHAR * pszTagName = szTitle;
305317 if ( editInfo->m_bIsGrep ){
306318 // Grepは検索キーとタグがぶつかることがあるので単に(Grep)と表示
@@ -409,7 +421,8 @@ DWORD CGrepAgent::DoGrep(
409421 }
410422 if ( bLineSelect ){
411423 int len = cmemReplace.GetStringLength ();
412- if ( cmemReplace[len - 1 ] != WCODE ::CR && cmemReplace[len - 1 ] != WCODE ::LF ){
424+ // 空クリップボード時に cmemReplace[len - 1] が範囲外参照になるのを防ぐ
425+ if ( len == 0 || (cmemReplace[len - 1 ] != WCODE ::CR && cmemReplace[len - 1 ] != WCODE ::LF ) ){
413426 cmemReplace.AppendString (pcViewDst->GetDocument ()->m_cDocEditor .GetNewLineCode ().GetValue2 ());
414427 }
415428 }
@@ -2612,6 +2625,34 @@ CNativeW CGrepAgent::BuildGrepFooter(int nHitCount, bool bGrepReplace)
26122625 return cmemMessage;
26132626}
26142627
2628+ #ifdef _DEBUG
2629+ // 0-5: 性能計測基盤(Debug 限定)
2630+ // 並列 Grep の 列挙時間 / 検索時間 / resultMutex ロック待ち / バッチ終端待ち を集計し、
2631+ // Grep 終了時に MYTRACE で出力する。Release ビルドでは一切のコードを生成しない。
2632+ struct SGrepPerfStats {
2633+ std::atomic<long long > enumUs{ 0 }; // ファイル列挙時間(メインスレッド、μ秒)
2634+ std::atomic<long long > searchUs{ 0 }; // ファイル内検索時間(全ワーカー合算、μ秒)
2635+ std::atomic<long long > lockWaitUs{ 0 }; // resultMutex ロック待ち(全ワーカー合算、μ秒)
2636+ std::atomic<long long > batchWaitUs{ 0 }; // バッチ終端待ち(メインスレッド、μ秒)
2637+ void Trace () const {
2638+ MYTRACE ( L" [GrepPerf] enum=%lldus search=%lldus lockWait=%lldus batchWait=%lldus\n " ,
2639+ enumUs.load (), searchUs.load (), lockWaitUs.load (), batchWaitUs.load () );
2640+ }
2641+ };
2642+ static long long GrepPerfNowUs ()
2643+ {
2644+ using namespace std ::chrono;
2645+ return duration_cast<microseconds>( steady_clock::now ().time_since_epoch () ).count ();
2646+ }
2647+ #define GREP_PERF_BEGIN ( var ) const long long var = GrepPerfNowUs()
2648+ #define GREP_PERF_ADD ( field, var ) perfStats.field.fetch_add( GrepPerfNowUs() - (var) )
2649+ #define GREP_PERF_TRACE () perfStats.Trace()
2650+ #else
2651+ #define GREP_PERF_BEGIN ( var ) ((void )0 )
2652+ #define GREP_PERF_ADD ( field, var ) ((void )0 )
2653+ #define GREP_PERF_TRACE () ((void )0 )
2654+ #endif
2655+
26152656/* ! @brief 並列Grep: スレッドプールでファイル検索を並列化する
26162657 @retval -1 キャンセル
26172658 @retval 0 完了
@@ -2647,6 +2688,11 @@ int CGrepAgent::RunParallelGrep(
26472688 // ヒット数(全バッチ共通・バッチ間で引き継ぐ)
26482689 std::atomic<int > atomicHitCount{ 0 };
26492690
2691+ #ifdef _DEBUG
2692+ // 0-5: 性能計測(Debug 限定)
2693+ SGrepPerfStats perfStats;
2694+ #endif
2695+
26502696 // ===== スレッドプール: バッチ間でスレッドを再利用し生成・破棄コストを排除 =====
26512697 // バッチ番号インクリメントで新バッチを通知し、condition_variable で待機中ワーカーを起床させる。
26522698 std::mutex poolMutex;
@@ -2772,20 +2818,24 @@ int CGrepAgent::RunParallelGrep(
27722818 // ファイル内検索
27732819 localMessage._SetStringLength (0 );
27742820 const SGrepSearchParams workerParams{ searchKey.c_str (), sSearchOption , sGrepOption };
2821+ GREP_PERF_BEGIN ( perfSearchStart );
27752822 const int fileHits = DoGrepFileWorker (
27762823 workerParams, task,
27772824 &localRegexp, localPattern,
27782825 bWorkCancelled,
27792826 localMessage, localUnicodeBuffer
27802827 );
2828+ GREP_PERF_ADD ( searchUs, perfSearchStart );
27812829
27822830 if ( fileHits == -1 ){
27832831 bWorkCancelled.store ( true );
27842832 break ;
27852833 }
27862834
27872835 if ( fileHits > 0 || localMessage.GetStringLength () > 0 ){
2836+ GREP_PERF_BEGIN ( perfLockStart );
27882837 std::scoped_lock lk ( resultMutex );
2838+ GREP_PERF_ADD ( lockWaitUs, perfLockStart );
27892839 // フォルダーヘッダー重複排除(最初のマッチ時のみ出力)
27902840 if ( sGrepOption .bGrepOutputBaseFolder &&
27912841 !writtenBaseFolders.contains (task.baseFolder ) ){
@@ -2826,6 +2876,14 @@ int CGrepAgent::RunParallelGrep(
28262876 cvInitDone.wait ( lk, [&]{ return nInitDone.load () >= nThreads; } );
28272877 }
28282878
2879+ // 全ワーカーが初期化に失敗した場合、結果 0 件のまま無警告で終了せずエラーとして中断する
2880+ if ( nActiveWorkers.load () == 0 ){
2881+ ErrorMessage ( pcViewDst != nullptr ? pcViewDst->m_hwndParent : nullptr ,
2882+ L" Grep: 検索用ワーカースレッドの初期化にすべて失敗しました。検索を中断します。" ); // TODO: Phase 5 E-1 で LS() 化
2883+ nHitCountOut = 0 ;
2884+ return -1 ;
2885+ }
2886+
28292887 // バッチ実行ラムダ: vecTasks をプールのワーカーに割り当て結果を出力する
28302888 // 戻り値: true=継続, false=キャンセル発生
28312889 auto RunBatch = [&]( const std::vector<SGrepFileTask>& vecTasks ) {
@@ -2843,6 +2901,7 @@ int CGrepAgent::RunParallelGrep(
28432901 cvPoolStart.notify_all ();
28442902
28452903 // メインスレッド: 全ワーカーが完全終了するまでUI更新・キャンセル監視・結果フラッシュを継続
2904+ GREP_PERF_BEGIN ( perfBatchStart );
28462905 while ( poolBatchActive.load () > 0 ){
28472906 ::Sleep (5 );
28482907
@@ -2875,6 +2934,7 @@ int CGrepAgent::RunParallelGrep(
28752934 cmemMessage._SetStringLength (0 );
28762935 }
28772936 }
2937+ GREP_PERF_ADD ( batchWaitUs, perfBatchStart );
28782938
28792939 // 最終フラッシュ
28802940 {
@@ -2909,12 +2969,14 @@ int CGrepAgent::RunParallelGrep(
29092969 sGrepOptionNoSub .bGrepSubFolder = false ;
29102970 bool bEnumCancelled = false ;
29112971 std::vector<SGrepFileTask> vecTasks;
2972+ GREP_PERF_BEGIN ( perfEnumStart );
29122973 DoGrepTreeEnumerate (
29132974 pcDlgCancel, sGrepOptionNoSub ,
29142975 SGrepEnumContext{ cGrepEnumKeys, cGrepExceptAbsFiles, cGrepExceptAbsFolders },
29152976 sPath .c_str (), sPath .c_str (),
29162977 vecTasks, bEnumCancelled
29172978 );
2979+ GREP_PERF_ADD ( enumUs, perfEnumStart );
29182980 if ( bEnumCancelled ){ nGrepTreeResult = -1 ; break ; }
29192981 if ( !RunBatch (vecTasks) ){ nGrepTreeResult = -1 ; break ; }
29202982 }
@@ -2925,20 +2987,24 @@ int CGrepAgent::RunParallelGrep(
29252987 }
29262988 CGrepEnumOptions cGrepEnumOptionsDir;
29272989 CGrepEnumFilterFolders cTopFolders;
2990+ GREP_PERF_BEGIN ( perfEnumDirStart );
29282991 cTopFolders.Enumerates (
29292992 sPath .c_str (), cGrepEnumKeys, cGrepEnumOptionsDir, cGrepExceptAbsFolders );
2993+ GREP_PERF_ADD ( enumUs, perfEnumDirStart );
29302994
29312995 const int nFolderCount = cTopFolders.GetCount ();
29322996 for ( int fi = 0 ; fi < nFolderCount && nGrepTreeResult != -1 ; fi++ ){
29332997 std::wstring subFolder = sPath + L" \\ " + cTopFolders.GetFileName (fi);
29342998 bool bEnumCancelled = false ;
29352999 std::vector<SGrepFileTask> vecTasks;
3000+ GREP_PERF_BEGIN ( perfEnumSubStart );
29363001 DoGrepTreeEnumerate (
29373002 pcDlgCancel, sGrepOption ,
29383003 SGrepEnumContext{ cGrepEnumKeys, cGrepExceptAbsFiles, cGrepExceptAbsFolders },
29393004 subFolder.c_str (), sPath .c_str (),
29403005 vecTasks, bEnumCancelled
29413006 );
3007+ GREP_PERF_ADD ( enumUs, perfEnumSubStart );
29423008 if ( bEnumCancelled ){ nGrepTreeResult = -1 ; break ; }
29433009 if ( !RunBatch (vecTasks) ){ nGrepTreeResult = -1 ; break ; }
29443010 }
@@ -2947,5 +3013,6 @@ int CGrepAgent::RunParallelGrep(
29473013 // スレッドプールのシャットダウンと join は PoolJoinGuard のデストラクタが
29483014 // 担う(正常終了・例外伝播のどちらの経路でも確実に実行される)。
29493015 nHitCountOut = atomicHitCount.load ();
3016+ GREP_PERF_TRACE ();
29503017 return nGrepTreeResult;
29513018}
0 commit comments