Напишите программу для получения размеров и уровней кэша ЦП.

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

  1. Выделить большой массив
  2. Получите доступ к части разного размера каждый раз.

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

#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) Core (TM) i3-2350M. Кэш-память L1: 32 КБ (для данных), Кэш-память L2 256 КБ, Кэш-память L3 3072 КБ.
Похоже, это не следует ни одному правилу. Я не могу получить информацию о размере кеша или уровне кеша от этого.
Кто-нибудь может помочь? Заранее спасибо.

Обновить:
Следуйте советам @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

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

9

Решение

Я не уверен, что это единственная проблема здесь, но она определенно самая большая — ваш код очень быстро вызовет предварительные выборки 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

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

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

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

6

Другие решения

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

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

4

Если вы используете GNU / Linux, вы можете просто прочитать содержимое файлов / Proc / CPUInfo и для дальнейших деталей / SYS / устройства / системы / CPU /*. В UNIX просто не определяют API, где простой файл может выполнить эту работу в любом случае.

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

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

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

2
По вопросам рекламы [email protected]