系统上的缓存大小估算?

时间:2014-01-23 04:37:40

标签: c performance caching cpu-cache

我从这个链接获得了这个程序(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 个答案:

答案 0 :(得分:10)

  1. 您需要直接访问内存

    我不是指 DMA 转移。内存必须由 CPU 访问(否则你不是在测量 CACHE ),而是直接测量...所以测量可能不会非常准确 Windows / Linux ,因为服务和其他进程在运行时可能会混淆缓存。测量多次并取平均值以获得更好的结果(或使用最快的时间或一起过滤)。为了获得最佳准确度,请使用 DOS asm ,例如

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

    所以你测量内存传输而不是代码中的其他东西!!!

  2. 测量原始传输时间并绘制图表

    • x轴是传输块大小
    • y轴是传输速度

    graph

    具有相同转移率的区域与适当的 CACHE 层一致

  3. [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两个定义来配置代码。

    图形输出:

    memory benchmark measured data

    为了最大限度地提高准确性,您可以将流程优先级更改为最大值。因此,创建具有最高优先级的度量线程(我尝试但实际上它会搞乱)并添加关键部分,以便测试不会经常被 OS 中断(没有线程的可见差异)。如果您想使用Byte传输,请考虑它只使用16bit个寄存器,因此您需要添加循环和地址迭代。

    <强> PS。

    如果您在笔记本电脑上尝试此操作,那么您应该使 CPU 过热,以确保以最高 CPU /内存速度进行测量。所以没有Sleep。测量前的一些愚蠢的循环会这样做,但它们应至少运行几秒钟。此外,您可以通过 CPU 频率测量同步并在上升时循环。在它饱和后停止......

    asm 指令RDTSC最适用于此(但要注意其含义因新体系结构而略有改变)。

    如果您不在 Windows 下,请将功能tbeg,tend更改为操作系统等效项

    [edit2]进一步提高准确性

    在最终解决 VCL 影响测量精度的问题之后,由于这个问题及其更多信息here我发现了这个问题,为了提高准确性,您可以在基准测试之前做到这一点:

    1. 将流程优先级设置为realtime

    2. 将进程关联设置为单CPU

      所以你只测量多核上的 CPU

    3. 刷新数据和指令CACHE

    4. 例如:

          // 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);
              }
      

      因此,更准确的测量结果如下:

      more accurate output

答案 1 :(得分:4)

您的lengthMod变量不符合您的预期。您希望它限制数据集的大小,但那里有2个问题 -

  • 使用2的幂进行按位AND将屏蔽除了打开的所有位之外的所有位。如果是例如lengthMod是1k(0x400),然后所有低于0x400的索引(意味着i = 1到63)将简单地映射到索引0,因此您将始终点击缓存。这可能是结果如此之快的原因。而是使用lengthMod - 1创建一个正确的掩码(0x400 - > 0x3ff,这将掩盖高位并保持较低位完整)。
  • lengthMod的某些值不是2的幂,因此执行lengthMod-1不会起作用,因为某些掩码位仍然为零。从列表中删除它们,或者使用模运算而不是lengthMod-1。有关类似案例,请参阅我的回答here

另一个问题是16B跳转可能不足以跳过cachline,因为大多数常见的CPU使用64字节的高速缓存行,因此每4次迭代只能获得一次。请改用(i*64)