2013-10-02 25 views
9

Chcę napisać program, aby uzyskać mój rozmiar pamięci podręcznej (L1, L2, L3). Znam ogólną ideę tego.Napisz program, aby uzyskać rozmiary i poziomy pamięci podręcznej procesora.

  1. Przeznaczyć dużym tablicę
  2. dostępu część, o różnej wielkości za każdym razem.

Więc napisałem mały program. Oto mój kod:

#include <cstdio> 
#include <time.h> 
#include <sys/mman.h> 

const int KB = 1024; 
const int MB = 1024 * KB; 
const int data_size = 32 * MB; 
const int repeats = 64 * MB; 
const int steps = 8 * MB; 
const int times = 8; 

long long clock_time() { 
    struct timespec tp; 
    clock_gettime(CLOCK_REALTIME, &tp); 
    return (long long)(tp.tv_nsec + (long long)tp.tv_sec * 1000000000ll); 
} 

int main() { 
    // allocate memory and lock 
    void* map = mmap(NULL, (size_t)data_size, PROT_READ | PROT_WRITE, 
        MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); 
    if (map == MAP_FAILED) { 
     return 0; 
    } 
    int* data = (int*)map; 

    // write all to avoid paging on demand 
    for (int i = 0;i< data_size/sizeof(int);i++) { 
     data[i]++; 
    } 

    int steps[] = { 1*KB, 4*KB, 8*KB, 16*KB, 24 * KB, 32*KB, 64*KB, 128*KB, 
        128*KB*2, 128*KB*3, 512*KB, 1 * MB, 2 * MB, 3 * MB, 4 * MB, 
        5 * MB, 6 * MB, 7 * MB, 8 * MB, 9 * MB}; 
    for (int i = 0; i <= sizeof(steps)/sizeof(int) - 1; i++) { 
     double totalTime = 0;  
     for (int k = 0; k < times; k++) { 
      int size_mask = steps[i]/sizeof(int) - 1; 
      long long start = clock_time(); 
      for (int j = 0; j < repeats; j++) { 
       ++data[ (j * 16) & size_mask ]; 
      } 
      long long end = clock_time(); 
      totalTime += (end - start)/1000000000.0; 
     } 
     printf("%d time: %lf\n", steps[i]/KB, totalTime); 
    } 
    munmap(map, (size_t)data_size); 
    return 0; 
} 

Jednak wynik jest więc dziwne:

1 time: 1.989998 
4 time: 1.992945 
8 time: 1.997071 
16 time: 1.993442 
24 time: 1.994212 
32 time: 2.002103 
64 time: 1.959601 
128 time: 1.957994 
256 time: 1.975517 
384 time: 1.975143 
512 time: 2.209696 
1024 time: 2.437783 
2048 time: 7.006168 
3072 time: 5.306975 
4096 time: 5.943510 
5120 time: 2.396078 
6144 time: 4.404022 
7168 time: 4.900366 
8192 time: 8.998624 
9216 time: 6.574195 

My CPU Intel (R) Core (TM) i3-2350M. L1 Cache: 32K (dla danych), L2 Cache 256K, L3 Cache 3072K. Wygląda na to, że nie jest zgodna z żadną regułą. Nie mogę uzyskać z tego informacji o rozmiarze pamięci podręcznej lub poziomie pamięci podręcznej. Czy ktokolwiek mógłby udzielić pomocy? Z góry dziękuję.

Aktualizacja: Obserwuj @Leeor rada, używam j*64 zamiast j*16. Nowe wyniki:

1 time: 1.996282 
4 time: 2.002579 
8 time: 2.002240 
16 time: 1.993198 
24 time: 1.995733 
32 time: 2.000463 
64 time: 1.968637 
128 time: 1.956138 
256 time: 1.978266 
384 time: 1.991912 
512 time: 2.192371 
1024 time: 2.262387 
2048 time: 3.019435 
3072 time: 2.359423 
4096 time: 5.874426 
5120 time: 2.324901 
6144 time: 4.135550 
7168 time: 3.851972 
8192 time: 7.417762 
9216 time: 2.272929 
10240 time: 3.441985 
11264 time: 3.094753 

Dwa piki, 4096K i 8192K. Wciąż dziwne.

Odpowiedz

5

Nie jestem pewien, czy jest to jedyny problem, ale jest zdecydowanie największy - Twój kod bardzo szybko uruchomi preselektory strumienia HW, dzięki czemu prawie zawsze trafisz w opóźnienia L1 lub L2.

Więcej szczegółów można znaleźć tutaj - http://software.intel.com/en-us/articles/optimizing-application-performance-on-intel-coret-microarchitecture-using-hardware-implemented-prefetchers

Dla odniesienia należy albo wyłączyć je (przez BIOS lub w jakikolwiek inny sposób), albo przynajmniej uczynić swoje kroki już zastępując j*16 (* 4 bajty na int = 64B, jedna linia pamięci podręcznej - klasyczny krok dla detektora strumienia), z j*64 (4 linie pamięci podręcznej). Powodem jest to, że prefetcher może wydać 2 przesłuchania na żądanie strumieniowe, więc wyprzedza twój kod, gdy wykonujesz kroki jednostkowe, może nadal być nieco lepszy od ciebie, gdy twój kod przeskakuje przez 2 linie, ale staje się w większości bezużyteczny z dłuższym czasem skoki (3 nie jest dobre ze względu na twój modulu, potrzebujesz dzielnika o rozmiarze kroku)

Zaktualizuj pytania z nowymi wynikami i możemy dowiedzieć się, czy jest tu coś jeszcze.


Edit1: Ok, wpadłem stały kod i dostał -

1 time: 1.321001 
4 time: 1.321998 
8 time: 1.336288 
16 time: 1.324994 
24 time: 1.319742 
32 time: 1.330685 
64 time: 1.536644 
128 time: 1.536933 
256 time: 1.669329 
384 time: 1.592145 
512 time: 2.036315 
1024 time: 2.214269 
2048 time: 2.407584 
3072 time: 2.259108 
4096 time: 2.584872 
5120 time: 2.203696 
6144 time: 2.335194 
7168 time: 2.322517 
8192 time: 5.554941 
9216 time: 2.230817 

To sprawia, że ​​dużo więcej sensu, jeśli ignorować kilka kolumn - skaczesz po (wielkość L1) 32k , ale zamiast skakać po 256k (rozmiar L2), uzyskujemy zbyt dobry wynik dla 384 i skaczemy tylko na 512k.Ostatni skok to 8M (mój rozmiar LLC), ale 9k znowu jest zepsuty.

Dzięki temu możemy wykryć następny błąd - ANDING z maską rozmiaru ma sens tylko wtedy, gdy ma moc 2, w przeciwnym razie nie zawija się, ale zamiast tego ponownie powtórzy niektóre z ostatnich adresów (co kończy się optymizmem wyniki, ponieważ są świeże w pamięci podręcznej).

spróbować wymienić ... & size_mask z % steps[i]/sizeof(int) The modulu jest droższe, ale jeśli chcesz mieć te rozmiary trzeba go (lub alternatywnie, wskaźnik biegu, który zostanie wyzerowany, gdy przekracza on aktualny rozmiar)

+0

Dziękujemy !. Zamiast tego używam j * 64, ale wynik pozostaje niejasny. Zobacz moją aktualizację. –

+0

@KanLiu - aktualizowana moja odpowiedź – Leeor

+1

wielki, to działa! Wielkie dzięki. –

4

Uważam, że lepiej będzie zapoznać się z instrukcją CPUID. To nie jest trywialne, ale powinny być informacje w Internecie.

Ponadto, jeśli używasz systemu Windows, możesz użyć funkcji GetLogicalProcessorInformation. Pamiętaj, że jest obecny tylko w Windows XP SP3 i nowszych. Nic nie wiem o systemie Linux/Unix.

+0

To dobry sposób. Ale naprawdę chcę zrozumieć, jak pamięć podręczna naprawdę działa i dlaczego mój kod działa w ten sposób. Dzięki i tak. –

+1

@KanLiu - Widziałeś [ten artykuł] (http://lwn.net/Articles/250967/)? Ma wszystkie szczegóły, w tym rzeczy podręczne. –

+0

Dziękujemy! Naprawdę dobre rzeczy. –

2

Jeśli używając GNU/Linux możesz po prostu odczytać zawartość plików /proc/cpuinfo oraz dalsze szczegóły: /sys/devices/system/cpu/*. W systemie UNIX powszechne jest nie definiowanie interfejsu API, w którym zwykły plik i tak może wykonać to zadanie.

Chciałbym również spojrzeć na źródła util-linux, zawiera program o nazwie lscpu. To powinno dać ci przykład, jak odzyskać wymagane informacje.

// aktualizuje
http://git.kernel.org/cgit/utils/util-linux/util-linux.git/tree/sys-utils/lscpu.c

Jeśli tylko podjąć spojrzeć na źródło ich. To w zasadzie czytanie z powyższego pliku, to wszystko. Dlatego też absolutnie poprawne jest czytanie z tych plików, są one dostarczane przez jądro.

+0

Dzięki. Jak już powiedziałem, rozmiary i poziomy pamięci podręcznej nie pasują do zachowania mojego kodu i chcę znać przyczynę. –