PAPI を使うと CPU のハードウェアカウンタを使って様々な値を測定できる。 例えば
- 実行した命令の数
- 実行した浮動小数点演算の数
- パイプラインがストールした回数
- キャッシュヒット/ミス の回数
- 分岐予測の成功/失敗数
を測定できる。測定した値は FLOPS の測定なりパフォーマンスのチューニングなりに使える。
適切に設定すれば CPU 以外のハードウェアのカウンタも読めるらしいが、使ってないのでよくわからない。
インストール
昔はインストールするのにカーネルにパッチを当てる必要があったりして一苦労だったらしい (検索すると出てくる) が、
最新版の Linux カーネルであればインストールは ./configure; make; make install
で済んだ。
利用可能なイベントの確認
papi_avail
で利用可能なイベントの一覧を確認できる。コンシューマ向けの CPU だと性能測定用のハードウェアカウンタがしょぼいようなので、インストールしたら必ず確認したほうがいい。
自分の環境では以下のようになった。
$ papi_avail
Available PAPI preset and user defined events plus hardware information.
--------------------------------------------------------------------------------
PAPI version : 5.6.1.0
Operating system : Linux 4.4.0-128-generic
Vendor string and code : GenuineIntel (1, 0x1)
Model string and code : Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz (158, 0x9e)
...
--------------------------------------------------------------------------------
================================================================================
PAPI Preset Events
================================================================================
Name Code Avail Deriv Description (Note)
PAPI_L1_DCM 0x80000000 Yes No Level 1 data cache misses
PAPI_L1_ICM 0x80000001 Yes No Level 1 instruction cache misses
PAPI_L2_DCM 0x80000002 Yes Yes Level 2 data cache misses
PAPI_L2_ICM 0x80000003 Yes No Level 2 instruction cache misses
PAPI_L3_DCM 0x80000004 No No Level 3 data cache misses
PAPI_L3_ICM 0x80000005 No No Level 3 instruction cache misses
...
PAPI_FML_INS 0x80000061 No No Floating point multiply instructions
PAPI_FAD_INS 0x80000062 No No Floating point add instructions
PAPI_FDV_INS 0x80000063 No No Floating point divide instructions
PAPI_FSQ_INS 0x80000064 No No Floating point square root instructions
PAPI_FNV_INS 0x80000065 No No Floating point inverse instructions
PAPI_FP_OPS 0x80000066 No No Floating point operations
PAPI_SP_OPS 0x80000067 Yes Yes Floating point operations; optimized to count scaled single precision vector operations
PAPI_DP_OPS 0x80000068 Yes Yes Floating point operations; optimized to count scaled double precision vector operations
PAPI_VEC_SP 0x80000069 Yes Yes Single precision vector/SIMD instructions
PAPI_VEC_DP 0x8000006a Yes Yes Double precision vector/SIMD instructions
PAPI_REF_CYC 0x8000006b Yes No Reference clock cycles
--------------------------------------------------------------------------------
Of 108 possible events, 59 are available, of which 18 are derived.
出力の最終行から、全体の約半分 (59 / 108) のイベントしか測定できないことが分かる。
なお PAPI_FP_OPS
という浮動小数点演算の数を数えるための一番基本的なイベントが使えないことから、
PAPI のサンプルコードやテストが上手く実行できなかった。
浮動小数点演算の回数自体は PAPI_DP_OPS
というイベントを使って測定できたのでよかったが、
一番最初に実行するサンプルコードが segmantation fault で落ちるので原因の調査にかなり時間を食った。
使う
以下のプログラムは浮動小数点演算の回数、CPU時間を計測し、そこから計算される GFLOPS と合わせて表示させた。 それから実行された命令の数も計測して表示させている。
#include <iostream>
#include <papi.h>
#include <vector>
void print_papi_error(int error_num) {
std::cout << "PAPI error " << error_num << ": " << PAPI_strerror(error_num)
<< "\n";
}
int main() {
int retval;
auto a = 0.0;
// 測定するイベント
std::vector<int> events = {PAPI_TOT_INS, PAPI_DP_OPS};
// 測定結果を収める場所
std::vector<long_long> values(events.size());
// カウンタをスタートさせる
// events が多すぎる場合はここでエラーが出る
retval = PAPI_start_counters(events.data(), events.size());
if (retval != PAPI_OK) {
print_papi_error(retval);
}
auto cpu_time_begin_usec = PAPI_get_virt_usec();
// 浮動小数点演算を 10^8 回行う
// 前後の命令が依存関係を持つので性能は出ない
for (auto i = 0; i < 1.e8; i++) {
a += 1;
}
auto cpu_time_end_usec = PAPI_get_virt_usec();
// カウンタの値を読む
// 値を読んでもカウンタは止まらない
retval = PAPI_read_counters(values.data(), events.size());
if (retval != PAPI_OK) {
print_papi_error(retval);
}
// 測定結果の表示
auto &number_of_executed_instructions = values[0];
auto &number_of_floating_point_operation = values[1];
auto elapsed_cpu_time_usec = cpu_time_end_usec - cpu_time_begin_usec;
auto GFLOPS = 1.e6 * number_of_floating_point_operation / elapsed_cpu_time_usec;
std::cout << "number_of_floating_point_operation = " << number_of_floating_point_operation << "\n";
std::cout << "elapsed_cpu_time_usec = " << elapsed_cpu_time_usec << "\n";
std::cout << "GFLOPS = " << GFLOPS * 1.e-9 << "\n";
std::cout << "number_of_executed_instructions = " << number_of_executed_instructions << "\n";
// カウンタをクリア
PAPI_stop_counters(values.data(), events.size());
}
number_of_floating_point_operation = 100000000
elapsed_cpu_time_usec = 264001
GFLOPS = 0.378786
number_of_executed_instructions = 1200002473
プログラムの出力のうち、 number_of_floating_point_operation
だけは何回測っても変わらないという素晴らしい性質を持つ。他の値は測るたびに変わってしまう。