Оценка размера кэша в вашей системе?
Я получил эту программу по этой ссылке ( https://gist.github.com/jiewmeng/3787223).I искал в Интернете идею для лучшего понимания кэшей процессора (L1 и L2). Я хочу быть смог написать программу, которая позволила бы мне угадать размер кеша L1 и L2 на моем новом ноутбуке (просто для учебы. Я знаю, что могу проверить спецификацию).
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define KB 1024
#define MB 1024 * 1024
int main() {
unsigned int steps = 256 * 1024 * 1024;
static int arr[4 * 1024 * 1024];
int lengthMod;
unsigned int i;
double timeTaken;
clock_t start;
int sizes[] = {
1 * KB, 4 * KB, 8 * KB, 16 * KB, 32 * KB, 64 * KB, 128 * KB, 256 * KB,
512 * KB, 1 * MB, 1.5 * MB, 2 * MB, 2.5 * MB, 3 * MB, 3.5 * MB, 4 * MB
};
int results[sizeof(sizes)/sizeof(int)];
int s;
/*for each size to test for ... */
for (s = 0; s < sizeof(sizes)/sizeof(int); s++)
{
lengthMod = sizes[s] - 1;
start = clock();
for (i = 0; i < steps; i++)
{
arr[(i * 16) & lengthMod] *= 10;
arr[(i * 16) & lengthMod] /= 10;
}
timeTaken = (double)(clock() - start)/CLOCKS_PER_SEC;
printf("%d, %.8f \n", sizes[s] / 1024, timeTaken);
}
return 0;
}
Вывод программы на моем компьютере выглядит следующим образом. Как мне интерпретировать числа? Что мне говорит эта программа?
1, 1.07000000
4, 1.04000000
8, 1.06000000
16, 1.13000000
32, 1.14000000
64, 1.17000000
128, 1.20000000
256, 1.21000000
512, 1.19000000
1024, 1.23000000
1536, 1.23000000
2048, 1.46000000
2560, 1.21000000
3072, 1.45000000
3584, 1.47000000
4096, 1.94000000
2 ответа
вам нужен прямой доступ к памяти
Я не имею в виду передачу DMA этим. Конечно, ЦП должен получать доступ к памяти (иначе вы не измеряете CACHE), но настолько непосредственно, насколько это возможно... поэтому измерения, вероятно, не будут очень точными в Windows/Linux, потому что службы и другие процессы могут связываться с кешами во время выполнения, Измеряйте много раз и усредняйте для лучших результатов (или используйте самое быстрое время или отфильтруйте его вместе). Для лучшей точности используйте DOS и ASM, например
rep + movsb,movsw,movsd rep + stosb,stosw,stosd
так вы измеряете передачу памяти, а не что-то еще как в вашем коде!!!
измерить сырое время передачи и построить график
x
ось - размер блока передачиy
ось - скорость передачи
зоны с одинаковой скоростью передачи соответствуют соответствующему уровню CACHE
[Edit1] не смог найти мой старый исходный код для этого, поэтому я сейчас что-то запустил в C++ для Windows:
Измерение времени:
//---------------------------------------------------------------------------
double performance_Tms=-1.0, // perioda citaca [ms]
performance_tms= 0.0; // zmerany cas [ms]
//---------------------------------------------------------------------------
void tbeg()
{
LARGE_INTEGER i;
if (performance_Tms<=0.0) { QueryPerformanceFrequency(&i); performance_Tms=1000.0/double(i.QuadPart); }
QueryPerformanceCounter(&i); performance_tms=double(i.QuadPart);
}
//---------------------------------------------------------------------------
double tend()
{
LARGE_INTEGER i;
QueryPerformanceCounter(&i); performance_tms=double(i.QuadPart)-performance_tms; performance_tms*=performance_Tms;
return performance_tms;
}
//---------------------------------------------------------------------------
Тест (32-битное приложение):
//---------------------------------------------------------------------------
DWORD sizes[]= // used transfer block sizes
{
1<<10, 2<<10, 3<<10, 4<<10, 5<<10, 6<<10, 7<<10, 8<<10, 9<<10,
10<<10, 11<<10, 12<<10, 13<<10, 14<<10, 15<<10, 16<<10, 17<<10, 18<<10,
19<<10, 20<<10, 21<<10, 22<<10, 23<<10, 24<<10, 25<<10, 26<<10, 27<<10,
28<<10, 29<<10, 30<<10, 31<<10, 32<<10, 48<<10, 64<<10, 80<<10, 96<<10,
112<<10,128<<10,192<<10,256<<10,320<<10,384<<10,448<<10,512<<10, 1<<20,
2<<20, 3<<20, 4<<20, 5<<20, 6<<20, 7<<20, 8<<20, 9<<20, 10<<20,
11<<20, 12<<20, 13<<20, 14<<20, 15<<20, 16<<20, 17<<20, 18<<20, 19<<20,
20<<20, 21<<20, 22<<20, 23<<20, 24<<20, 25<<20, 26<<20, 27<<20, 28<<20,
29<<20, 30<<20, 31<<20, 32<<20,
};
const int N=sizeof(sizes)>>2; // number of used sizes
double pmovsd[N]; // measured transfer rate rep MOVSD [MB/sec]
double pstosd[N]; // measured transfer rate rep STOSD [MB/sec]
//---------------------------------------------------------------------------
void measure()
{
int i;
BYTE *dat; // pointer to used memory
DWORD adr,siz,num; // local variables for asm
double t,t0;
HANDLE hnd; // process handle
// enable priority change (huge difference)
#define measure_priority
// enable critical sections (no difference)
// #define measure_lock
for (i=0;i<N;i++) pmovsd[i]=0.0;
for (i=0;i<N;i++) pstosd[i]=0.0;
dat=new BYTE[sizes[N-1]+4]; // last DWORD +4 Bytes (should be 3 but i like 4 more)
if (dat==NULL) return;
#ifdef measure_priority
hnd=GetCurrentProcess(); if (hnd!=NULL) { SetPriorityClass(hnd,REALTIME_PRIORITY_CLASS); CloseHandle(hnd); }
Sleep(200); // wait to change take effect
#endif
#ifdef measure_lock
CRITICAL_SECTION lock; // lock handle
InitializeCriticalSectionAndSpinCount(&lock,0x00000400);
EnterCriticalSection(&lock);
#endif
adr=(DWORD)(dat);
for (i=0;i<N;i++)
{
siz=sizes[i]; // siz = actual block size
num=(8<<20)/siz; // compute n (times to repeat the measurement)
if (num<4) num=4;
siz>>=2; // size / 4 because of 32bit transfer
// measure overhead
tbeg(); // start time meassurement
asm {
push esi
push edi
push ecx
push ebx
push eax
mov ebx,num
mov al,0
loop0: mov esi,adr
mov edi,adr
mov ecx,siz
// rep movsd // es,ds already set by C++
// rep stosd // es already set by C++
dec ebx
jnz loop0
pop eax
pop ebx
pop ecx
pop edi
pop esi
}
t0=tend(); // stop time meassurement
// measurement 1
tbeg(); // start time meassurement
asm {
push esi
push edi
push ecx
push ebx
push eax
mov ebx,num
mov al,0
loop1: mov esi,adr
mov edi,adr
mov ecx,siz
rep movsd // es,ds already set by C++
// rep stosd // es already set by C++
dec ebx
jnz loop1
pop eax
pop ebx
pop ecx
pop edi
pop esi
}
t=tend(); // stop time meassurement
t-=t0; if (t<1e-6) t=1e-6; // remove overhead and avoid division by zero
t=double(siz<<2)*double(num)/t; // Byte/ms
pmovsd[i]=t/(1.024*1024.0); // MByte/s
// measurement 2
tbeg(); // start time meassurement
asm {
push esi
push edi
push ecx
push ebx
push eax
mov ebx,num
mov al,0
loop2: mov esi,adr
mov edi,adr
mov ecx,siz
// rep movsd // es,ds already set by C++
rep stosd // es already set by C++
dec ebx
jnz loop2
pop eax
pop ebx
pop ecx
pop edi
pop esi
}
t=tend(); // stop time meassurement
t-=t0; if (t<1e-6) t=1e-6; // remove overhead and avoid division by zero
t=double(siz<<2)*double(num)/t; // Byte/ms
pstosd[i]=t/(1.024*1024.0); // MByte/s
}
#ifdef measure_lock
LeaveCriticalSection(&lock);
DeleteCriticalSection(&lock);
#endif
#ifdef measure_priority
hnd=GetCurrentProcess(); if (hnd!=NULL) { SetPriorityClass(hnd,NORMAL_PRIORITY_CLASS); CloseHandle(hnd); }
#endif
delete dat;
}
//---------------------------------------------------------------------------
Где массивы pmovsd[]
а также pstosd[]
держит измеренный 32bit
скорость передачи [MByte/sec]
, Вы можете настроить код, используя /rem два определения в начале функции измерения.
Графический вывод:
Для максимальной точности вы можете изменить класс приоритета процесса на максимальный. Так что создайте поток измерений с максимальным приоритетом (я пробую это, но на самом деле это все испортило) и добавьте в него критический раздел, чтобы тест не прерывался операционной системой так часто (без видимой разницы с потоками и без них). Если вы хотите использовать Byte
переводы затем принять во внимание, что он использует только 16bit
регистры, так что вам нужно добавить итерации цикла и адреса.
PS.
Если вы попробуете это на ноутбуке, вам следует перегреть процессор, чтобы убедиться, что вы измеряете максимальную скорость CPU/Mem. Так нет Sleep
s. Некоторые глупые циклы перед измерением сделают это, но они должны работать не менее нескольких секунд. Также вы можете синхронизировать это с помощью измерения частоты процессора и петли во время нарастания. Остановись после насыщения...
инструкция asm RDTSC
лучше всего для этого (но будьте осторожны, его значение немного изменилось с новыми архитектурами).
Если вы не под Windows, то измените функции tbeg,tend
к вашим OS эквивалентам
[edit2] дальнейшие улучшения точности
Ну а после окончательного решения проблемы с VCL, влияющей на точность измерения, которую я обнаружил благодаря этому вопросу и более подробно об этом здесь, улучшить точность вы можете до эталонного теста:
установить класс приоритета процесса на
realtime
установить привязку процесса к одному процессору
так что вы измеряете только один процессор на многоядерных
сбросить данные и кэши инструкций
Например:
// before mem benchmark
DWORD process_affinity_mask=0;
DWORD system_affinity_mask =0;
HANDLE hnd=GetCurrentProcess();
if (hnd!=NULL)
{
// priority
SetPriorityClass(hnd,REALTIME_PRIORITY_CLASS);
// affinity
GetProcessAffinityMask(hnd,&process_affinity_mask,&system_affinity_mask);
process_affinity_mask=1;
SetProcessAffinityMask(hnd,process_affinity_mask);
GetProcessAffinityMask(hnd,&process_affinity_mask,&system_affinity_mask);
}
// flush CACHEs
for (DWORD i=0;i<sizes[N-1];i+=7)
{
dat[i]+=i;
dat[i]*=i;
dat[i]&=i;
}
// after mem benchmark
if (hnd!=NULL)
{
SetPriorityClass(hnd,NORMAL_PRIORITY_CLASS);
SetProcessAffinityMask(hnd,system_affinity_mask);
}
Таким образом, более точное измерение выглядит так:
Ваш lengthMod
переменная не делает то, что вы думаете, что делает. Вы хотите ограничить размер вашего набора данных, но у вас есть 2 проблемы -
- Выполнение побитового И со степенью 2 маскирует все биты, кроме включенного. Если, например,
lengthMod
равен 1k (0x400), тогда все индексы ниже 0x400 (то есть от i=1 до 63) будут просто отображаться на индекс 0, поэтому вы всегда попадете в кэш. Наверное, поэтому результаты такие быстрые. Вместо этого используйтеlengthMod - 1
создать правильную маску (0x400 -> 0x3ff, которая бы маскировала только верхние биты и оставила бы младшие нетронутыми). - Некоторые из значений для
lengthMod
не степень 2, поэтому делаетlengthMod-1
не будет работать там, так как некоторые биты маски все равно были бы нулями. Или удалите их из списка, или используйте операцию по модулю вместоlengthMod-1
в целом. Смотрите также мой ответ здесь для аналогичного случая.
Другая проблема заключается в том, что переходов 16B, вероятно, недостаточно для пропуска кэширования, поскольку большинство обычных процессоров работают с 64-байтовыми линиями кэширования, поэтому вы получаете только один промах за каждые 4 итерации. использование (i*64)
вместо.