2018-06-19 - 2018-07-13 (update) |
|
|
高速化の解説一覧:[link:高速化]
プログラムの高速化は,処理のボトルネックの分析から始まります.
そして,分析の基本は処理時間の計測です.ここでは,その方法について解説します.
*時間計測用の関数
clock関数,QueryPerformanceCounter関数,std::chrono について紹介します.
** clock
C/C++で処理時間の計測を行う時,time.h で定義されている clock()関数が良く利用されます.
{#
#include <stdio.h>
#include <time.h>
int main() {
clock_t start = clock();
// 何かの処理
clock_t end = clock();
const double time = static_cast<double>(end - start) / CLOCKS_PER_SEC * 1000.0;
printf("time %lf[ms]\n", time);
return 0;
}
#}
ただ,clock()関数の分解能は10[ms]程度ですので,短い処理の計測には向きません.clock関数を利用して短い処理の時間を計測したい場合,例えば下記のように繰り返し処理にして,後で掛かった時間を繰り返し回数で割るような方法を考える必要があります.
{#
clock_t start = clock();
const int loop = 100
for(int i = 0; i < loop; i++){
// 何かの処理
}
clock_t end = clock();
const double time = static_cast<double>(end - start) / CLOCKS_PER_SEC * 1000.0 / loop;
printf("time %lf[ms]\n", time);
#}
** QueryPerformanceCounter
windows.hで定義されている関数で,1[ms]以下の細かい分解能で時間計測が可能です.clock関数に比べると使用法がやや複雑ですが,精度は高いためwindows環境であれば採用を検討しても良いと思います.
{#
#include <stdio.h>
#include <windows.h>
int main() {
// QueryPerformanceCounter関数の1秒当たりのカウント数を取得する
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;
QueryPerformanceCounter(&start);
// 何かの処理
QueryPerformanceCounter(&end);
double time = static_cast<double>(end.QuadPart - start.QuadPart) * 1000.0 / freq.QuadPart;
printf("time %lf[ms]\n", time);
return 0;
}
#}
** std::chrono
<chrono>で定義されているクラスで,1[ms]程度の分解能で時間計測が可能です.C++11をコンパイルできる環境があれば利用できるため,クロスプラットフォームを考える場合は有用だと思います.
{#
#include <stdio.h>
#include <chrono>
int main() {
using namespace std;
chrono::system_clock::time_point start, end;
start = chrono::system_clock::now();
// 何かの処理
end = chrono::system_clock::now();
double time = static_cast<double>(chrono::duration_cast<chrono::microseconds>(end - start).count() / 1000.0);
printf("time %lf[ms]\n", time);
return 0;
}
#}
**分解能の比較
{{small:注意:私の実験環境で確認した結果をもとに表を作りました.あくまで参考値として見てください.}}
{|
方法 && 分解能 ||
clock && 10ms程度 ||
QueryPerformanceCounter && 1ms以下 ||
std::chrono&& 1ms程度
|}
*プロファイラ
visual studioやgccなどのコンパイラには,処理時間の分析をしてくれるプロファイラと呼ばれるツールが備わっています.ここではその利用方法について解説します.
**テストコード
下記のごく簡単なテストコードを入力に,プロファイラの動作を確認してみます.
{#
void task(int *dat, const int w, const int h) {
for (int v = 0; v < h; v++) {
for (int u = 0; u < w; u++) {
dat[v * w + u] = u + v;
}
}
}
int main() {
const int w = 640;
const int h = 480;
int *dat = new int[w * h];
for (int i = 0; i < 100000; i++) {
task(dat, w, h);
}
delete[] dat;
return 0;
}
#}
**visual studio パフォーマンスプロファイラ
プログラムの各関数に掛かる処理時間を一覧する機能や,処理の重いホットパスを分析できます.分析結果はGUIで確認できます.
{{small:公式チュートリアル:[link:https://docs.microsoft.com/ja-jp/visualstudio/profiling/beginners-guide-to-performance-profiling] }}
***使い方の例
まず,visual studio上で上記テストコードをビルドし実行できる状態にしてください.次にvisual studioのメニューバーの「デバッグ」→「パフォーマンスプロファイラ」をクリックすると図1の画面に遷移します.
[img:brkh]
{{small:図1 パフォーマンスプロファイラのスタート画面}}
この画面で,分析ターゲットはスタートアッププロジェクトとし,CPU使用率にチェックを入れます.
開始ボタンを押すと,プログラムが起動し,その終了後しばらく待つと図2のような分析結果が出力されます.
[img:twfj]
{{small:図2 分析結果の最初の画面}}
ここでは,CPU使用率や各関数に掛かった処理時間などが表示されます.図2の「詳細なレポートを作成します.」をクリックすると図3のホットスパスの概要画面に遷移します.
[img:mbpz]
{{small:図3 分析結果のホットパスの概要画面}}
図3を見ると task とある部分に炎のマークがついています.この関数がホットパスになっているようです.
{{small:今回のテストコードは実質 task の関数しか呼び出していないため,これは当然の結果です.}}
taskの関数をクリックすると,図4に示す様にプログラムの行単位で処理時間に影響を与える場所が分かります.
[img:h37z]
{{small:図4 分析結果のホットパスの詳細画面}}
ホットパスが分かると,いろいろと高速化の手段も見えてくると思います.
{{small:ただ,意外と使いこなしが難しいです.例えば,コンパイルの最適化オプションによっては,細かな処理時間の分析ができない場合があります.}}
*時間計測用の関数とプロファイラの使い分け
好みによりますが,
①まずはプロファイラでプログラム全体から処理の重い部分を見つけ
②次にその部分に処理時間の計測用の関数を設置して処理の高速化の試行錯誤を行う
という使い分けが良いのではないかなと思います.
プロファイラ自体の起動がそれなりに重いので,ホットパスが分かった時点で処理時間の確認方法を切り替える作戦です.
>> ご意見・ご質問など お気軽にご連絡ください.info