Pracuję nad projektem, w którym musimy wdrożyć algorytm sprawdzony w teorii jako przyjazny dla pamięci podręcznej. Mówiąc prościej, jeśli wejściem jest N
i B
jest liczbą elementów, które są przesyłane między pamięcią podręczną a pamięcią RAM za każdym razem, gdy brakuje pamięci podręcznej, algorytm będzie wymagał dostępu do pamięci RAM przez O(N/B)
.Dlaczego Perf i Papi podają różne wartości odniesień do pamięci podręcznej L3 i chybiają?
Chciałbym pokazać, że tak naprawdę jest to zachowanie w praktyce. Aby lepiej zrozumieć, jak można mierzyć różne liczniki sprzętowe związane z pamięcią podręczną, postanowiłem użyć różnych narzędzi. Jedna to Perf, a druga to biblioteka PAPI. Niestety im więcej pracuję z tymi narzędziami, tym mniej rozumiem, co dokładnie robią.
Używam procesora Intel (R) Core i5-3470 @ 3.20GHz z 8 GB pamięci RAM, pamięci podręcznej L1 256 KB, pamięci podręcznej L2 1 MB, pamięci podręcznej L3 6 MB. Rozmiar linii pamięci podręcznej wynosi 64 bajty. Przypuszczam, że musi to być rozmiar bloku B
.
Spójrzmy na poniższy przykład:
#include <iostream>
using namespace std;
struct node{
int l, r;
};
int main(int argc, char* argv[]){
int n = 1000000;
node* A = new node[n];
int i;
for(i=0;i<n;i++){
A[i].l = 1;
A[i].r = 4;
}
return 0;
}
Każdy węzeł wymaga 8 bajtów, co oznacza, że linia cache zmieści 8 węzłów, więc należy oczekiwać około 1000000/8 = 125000
L3 cache tęskni.
Bez optymalizacji (bez -O3
), to wyjście z perf:
perf stat -B -e cache-references,cache-misses ./cachetests
Performance counter stats for './cachetests':
162,813 cache-references
142,247 cache-misses # 87.368 % of all cache refs
0.007163021 seconds time elapsed
Jest dość blisko tego, co oczekujemy. Teraz przypuśćmy, że używamy biblioteki PAPI.
#include <iostream>
#include <papi.h>
using namespace std;
struct node{
int l, r;
};
void handle_error(int err){
std::cerr << "PAPI error: " << err << std::endl;
}
int main(int argc, char* argv[]){
int numEvents = 2;
long long values[2];
int events[2] = {PAPI_L3_TCA,PAPI_L3_TCM};
if (PAPI_start_counters(events, numEvents) != PAPI_OK)
handle_error(1);
int n = 1000000;
node* A = new node[n];
int i;
for(i=0;i<n;i++){
A[i].l = 1;
A[i].r = 4;
}
if (PAPI_stop_counters(values, numEvents) != PAPI_OK)
handle_error(1);
cout<<"L3 accesses: "<<values[0]<<endl;
cout<<"L3 misses: "<<values[1]<<endl;
cout<<"L3 miss/access ratio: "<<(double)values[1]/values[0]<<endl;
return 0;
}
Jest to wyjście, które mam:
L3 accesses: 3335
L3 misses: 848
L3 miss/access ratio: 0.254273
Skąd taka duża różnica pomiędzy tymi dwoma narzędziami?
Czy próbowałeś licząc rok do roku panien danych za pomocą PAPI_L3_DCA i PAPI_L3_DCM? – HazemGomaa
tylko PAPI_L3_DCA jest dostępny i wydaje się, że daje około tej samej liczby – jsguy