PAPI を使用して L1 キャッシュ アクセスの結果をカウントしています。ほとんどのネイティブ イベントでは期待どおりの結果が得られますが、L1_MISS が正確でない場合が 1 つあります。コードに示すように、サイズが 64 のオブジェクトと 100,000 要素の揮発性配列があります。
typedef struct _object{
int value;
char pad[60];
} object;
#define arr_size 100000
volatile object array [arr_size];
void * loop (int arg){
/* Threads are set in NUMA2 */
int temp;
for(int i=0; i < arr_size; i++){
temp = array[i].value;
}
}
2 つの NUMA ノードを備えた Skylake プロセッサでテストしています。プリフェッチャーを無効にしました。gcc -O3 でコンパイルします。シナリオは次のとおりです。NUMA1 に設定されたメイン プロセスから、配列を初期化し、キャッシュ ラインをフラッシュします。次に、ループ関数を呼び出して NUMA2 から同じ配列を読み取る 5 つのスレッドを作成します。それらがすべて終了したら、メイン プロセスから配列をループして各要素を読み取り、L1 キャッシュ アクセスの結果を監視します。
int main(int argc, char* argv[]){
/* Main thread is set in NUMA1 */
/* Array is initialized and flushed from the cache*/
/* 5 threads are created with pthread_create, that call loop function,
and waited to finish by calling pthread_join*/
int tmp;
/*Hardware counters are counted for this loop*/
for(int i=0; i < arr_size; i++){
tmp = array[i].value;
}
}
次の 5 つのネイティブ イベント カウンターを読み取ります。
MEM_INST_RETIRED.ALL_LOADS: 100095
L1D.REPLACEMENT: 100246
MEM_LOAD_RETIRED.L1_HIT: 113
MEM_LOAD_RETIRED.L1_MISS: 56
MEM_LOAD_RETIRED.FB_HIT: 55
L1_MISS は約 100,000 になると予想されていました。これは、要素がキャッシュにフェッチされず、メインでのこの読み取りによってミスが発生するためです。また、ALL_LOADS は、L1_HIT + L1_MISS + FB_HIT の 3 つのカウンターの合計と等しくありません。この場合、L1D.REPLACEMENT は L1D データ行の置換をカウントすることで理にかなっているように見えますが、有効にするとプリフェッチもカウントされるため、私は納得できません。
この特定のシナリオでのみ、MEM_LOAD_RETIRED.L1_MISS カウンターがメインの読み取り操作によって発生したイベントを認識しない理由がわかりません。たとえば、NUMA2 のスレッドが読み取りの代わりに配列要素を変更すると、同じループで L1_MISS: 99818 が返されます。コードの主要なスケルトンを提供しようとしました。コメントされたポイントの一部が重要な場合は、それらも追加できます。