Оценка размера кэша в вашей системе?

Я получил эту программу по этой ссылке ( 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 ответа

Решение
  1. вам нужен прямой доступ к памяти

    Я не имею в виду передачу DMA этим. Конечно, ЦП должен получать доступ к памяти (иначе вы не измеряете CACHE), но настолько непосредственно, насколько это возможно... поэтому измерения, вероятно, не будут очень точными в Windows/Linux, потому что службы и другие процессы могут связываться с кешами во время выполнения, Измеряйте много раз и усредняйте для лучших результатов (или используйте самое быстрое время или отфильтруйте его вместе). Для лучшей точности используйте DOS и ASM, например

    rep + movsb,movsw,movsd 
    rep + stosb,stosw,stosd
    

    так вы измеряете передачу памяти, а не что-то еще как в вашем коде!!!

  2. измерить сырое время передачи и построить график

    • 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, влияющей на точность измерения, которую я обнаружил благодаря этому вопросу и более подробно об этом здесь, улучшить точность вы можете до эталонного теста:

  1. установить класс приоритета процесса на realtime

  2. установить привязку процесса к одному процессору

    так что вы измеряете только один процессор на многоядерных

  3. сбросить данные и кэши инструкций

Например:

    // 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) вместо.

Другие вопросы по тегам