私が開発しているいくつかのプラットフォームには、プロファイリング ツールがありません。プロファイラーを使用せずに、ホットスポットを特定するために個人的に使用した提案/テクニックを探しています。
ターゲット言語は C++ です。
私はあなたが個人的に使用したものに興味があります。
冗談ではありません:std :: coutやその他のテキスト/データ指向のアプローチにタイミングをダンプすることに加えて、Beep()関数も使用します。2つの「ビープ」チェックポイント間の沈黙のギャップを聞くことには、異なる種類の印象を与える何かがあります。
それは、書かれた楽譜を見ることと実際に音楽を聞くことの違いのようなものです。これは、rgb(255,0,0)を読み取ることと消防車を赤く表示することの違いのようなものです。
だから、今、私はクライアント/サーバーアプリを持っていて、さまざまな頻度のビープ音で、クライアントがメッセージを送信する場所、サーバーが応答を開始する場所、応答を終了する場所、応答が最初にクライアントに入る場所などをマークします。当然、時間がどこで費やされているかを感じることができます。
私は次のことが非常に役立つことを発見しました:
#ifdef PROFILING
# define PROFILE_CALL(x) do{ \
const DWORD t1 = timeGetTime(); \
x; \
const DWORD t2 = timeGetTime(); \
std::cout << "Call to '" << #x << "' took " << (t2 - t1) << " ms.\n"; \
}while(false)
#else
# define PROFILE_CALL(x) x
#endif
これは、呼び出し元の関数で次のように使用できます。
PROFILE_CALL(renderSlow(world));
int r = 0;
PROFILE_CALL(r = readPacketSize());
要するに、プロファイリング ツールが利用できない場合は、プロファイラーが行うことをエミュレートします。興味深いと思われる関数にカウンターを挿入し、回数を数えます。潜在的に、呼び出される引数のサイズ/種類を指定します。
プラットフォームのタイマーにアクセスできる場合、コードから不明な場合は、実行時間情報も取得するために、これらの関数の開始/終了時にこれらを開始/停止できます。通常、すべての機能をインストルメント化するには関数が多すぎるため、これにより、複雑なコードの費用対効果が最大になります。代わりに、タイマーを各セクション専用にすることで、コードの特定のセクションで費やされた時間を取得できます。
これら 2 つの手法を組み合わせることで、タイマーを使用してサイクルの大部分を消費するコードの広範なセクションを見つけ、個々の関数をより細かい粒度でインストルメント化して問題を解決する、反復的なアプローチを形成できます。
持続時間が十分に長い場合(たとえば、1分以上)、デバッガーでソフトウェアを実行してから数回中断し、デバッガーがどこで中断するかを確認します。これにより、ソフトウェアが何をしているのかが非常に大まかにわかります(例: 10回ブレークし、それらがすべて同じ場所にある場合、これは何か面白いことを教えてくれます!)。非常にラフで準備ができていますが、ツールや計装などは必要ありません。
どのプラットフォームを念頭に置いていたかはわかりませんが、組み込みマイクロコントローラーでは、予備のデジタル出力ラインを調整し、オシロスコープ、カウンター/タイマー、またはロジック アナライザーを使用してパルス幅を測定すると役立つ場合があります。
私は 80/20 ルールを使用して、ホットスポットや興味深いコール パスの周りにタイマーを配置します。ボトルネックがどこにあるのか (または少なくとも実行パスの大部分) を把握し、適切なプラットフォーム依存の高解像度タイマー (QueryPerformanceCounters、gettimeofday など) を使用する必要があります。
私は通常、起動時またはシャットダウン時に何も気にせず (必要でない限り)、明確に定義された「チョーク ポイント」、通常はメッセージ パッシングまたは何らかのアルゴリズム計算を行います。一般的に、メッセージ シンク/ソース (さらにシンク)、キュー、ミューテックス、および単純な混乱 (アルゴリズム、ループ) が、通常、実行パスのレイテンシの大部分を占めることがわかりました。
Visual Studio を使用していますか?
/Gh および /GH スイッチを使用できます。スタック検査を含む例を次に示します
これらのフラグを使用すると、ファイルごとに、実行時にメソッドが開始または終了されるたびに呼び出される装飾されていない関数を登録できます。
これにより、タイミング情報だけでなく、すべての時間のプロファイリング情報を登録できます。スタック ダンプ、呼び出しアドレス、リターン アドレスなど。関数 X で費やされた合計時間だけでなく、「関数 X が関数 Z の下で Y 時間を使用した」ことを知りたい場合があるため、これは重要です。