2013-10-02 1 views
9

Я хочу написать программу для получения размера кеша (L1, L2, L3). Я знаю общую идею этого.Напишите программу для получения размеров и уровней кеша процессора

  1. Выделяют большой массив
  2. Access часть его разного размера, каждый раз.

Так что я написал небольшую программу. Вот мой код:

#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; 
} 

Однако результат так странно:

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 

Мой процессор Intel (R) ядро ​​(TM) i3-2350M. Кэш L1: 32K (для данных), L2 Cache 256K, L3 Cache 3072K. Кажется, что это не соответствует никакому правилу. Я не могу получить информацию о размере кеша или уровне кеширования. Может ли кто-нибудь помочь? Заранее спасибо.

Обновление: Следуйте @Leeor советы, я использую j*64 вместо j*16. Новые результаты:

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 

Два пика, 4096K и 8192K. Все еще странно.

ответ

5

Я не уверен, что это единственная проблема здесь, но это определенно самый большой из них: ваш код очень быстро запускает префетеры потока HW, что делает вас почти всегда попаданием в L1 или L2 задержки.

Более подробную информацию можно найти здесь - http://software.intel.com/en-us/articles/optimizing-application-performance-on-intel-coret-microarchitecture-using-hardware-implemented-prefetchers

Для вашего теста Вы должны либо отключить их (через BIOS или любых других средств), или по крайней мере сделать ваши шаги больше, заменив j*16 (* 4 байта на INT = 64B, одна строка кеша - классический шаг блока для детектора потока), с j*64 (4 строки кэша). Причина заключается в том, что prefetcher может выдавать 2 prefetches на запрос потока, поэтому он бежит впереди вашего кода, когда вы делаете шаги по блоку, все равно может немного опередить вас, когда ваш код прыгает через 2 строки, но становится в основном бесполезным с более длинным прыжки (3 не хороши из-за вашего модулю, вам нужен разделитель step_size)

Обновите вопросы новыми результатами, и мы можем выяснить, есть ли что-нибудь еще.


EDIT1: Хорошо, я побежал фиксированный код и получил -

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 

Это делает гораздо больше смысла, если вы игнорируете несколько столбцов - вы прыгаете после (размер L1) 32k , но вместо того, чтобы прыгать после 256k (размер L2), мы получаем слишком хороший результат для 384 и прыгаем только на 512k.Последний прыжок в 8M (мой размер LLC), но 9k снова сломается.

Это позволяет нам обнаружить следующую ошибку. ИБС с маской размера имеет смысл только тогда, когда она имеет силу 2, в противном случае вы не обертываетесь, а вместо этого повторяете некоторые из последних адресов (что заканчивается оптимистично результаты, так как он свежий в кеше).

Попробуйте заменить ... & size_mask с % steps[i]/sizeof(int), то modulu дороже, но если вы хотите, чтобы эти размеры вам это нужно (или, альтернативно, индекс бега, который получает обнулить всякий раз, когда он превышает текущий размер)

+0

Спасибо !. Вместо этого я использую j * 64, но результат остается запутанным. См. Мое обновление. –

+0

@KanLiu - обновил мой ответ – Leeor

+1

отлично, он работает! Большое спасибо. –

4

Я думаю, вам будет лучше смотреть на инструкцию CPUID. Это не тривиально, но в Интернете должна быть информация.

Кроме того, если вы используете Windows, вы можете использовать функцию GetLogicalProcessorInformation. Имейте в виду, что он присутствует только в Windows XP SP3 и выше. Я ничего не знаю о Linux/Unix.

+0

Это хороший способ. Но я действительно хочу понять, как кеш действительно работает подробно и почему мой код ведет себя так. Спасибо, в любом случае. –

+1

@KanLiu - Вы видели [эту статью] (http://lwn.net/Articles/250967/)? В нем есть все подробности, в том числе материалы кешей. –

+0

Спасибо! Действительно хороший материал. –

2

Если вы Используя GNU/Linux, вы можете просто прочитать содержимое файлов /proc/cpuinfo и для получения дополнительной информации /sys/devices/system/cpu/*. В UNIX просто не определено API, где простой файл может выполнять эту работу в любом случае.

я бы также взглянуть на источник Util-Linux, он содержит программу под названием lscpu. Это должно дать вам пример, как получить необходимую информацию.

// обновить
http://git.kernel.org/cgit/utils/util-linux/util-linux.git/tree/sys-utils/lscpu.c

Если только взглянули на источник их. Это в основном чтение из упомянутого выше файла, вот и все. Поэтому абсолютно верно читать также из этих файлов, они предоставляются ядром.

+0

Спасибо. Как я уже сказал, размеры и уровни кеша не соответствуют поведению моего кода, и я хочу знать причину. –