系统上的缓存大小估算?

我从这个链接( https://gist.github.com/jiewmeng/3787223)获得了这个程序。我一直在网上搜索,以便更好地理解处理器缓存(L1和L2)。我想成为能够编写一个程序,让我能够猜测我的新笔记本电脑上L1和L2缓存的大小。(仅用于学习目的。我知道我可以查看规格。)

#include  #include  #include  #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 

  1. 你需要直接访问内存

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

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

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

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

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

    图形

    具有相同传输速率的区域与适当的CACHE层一致

[Edit1]无法找到我的旧源代码,所以我现在在C ++ for 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>=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两个定义来配置代码。

图形输出:

记忆基准测量数据

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

PS。

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

asm指令RDTSC是最好的(但要注意它的含义随着新架构略有改变)。

如果您不在Windows下,则更改functiontbeg,tend您的操作系统等效项

[edit2]进一步提高准确性

好吧,经过最终解决VCL影响测量精度的问题,我发现这要归功于这个问题以及更多关于它的问题 ,为了提高准确性,你可以在基准测试之前做到这一点:

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

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

    所以你只测量多核上的单个CPU

  3. 刷新DATA和指令CACHE

例如:

  // 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 

所以更准确的测量看起来像这样:

更准确的输出

你的lengthMod变量不符合你的想法。 您希望它限制数据集的大小,但您有2个问题 –

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

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