通过一个程序说明cache命中率对内存访问速度的影响
我们知道,现代的CPU包含cache部件,cache亦称高速缓冲,其中存储了CPU最近访问过的或者将要访问的数据。和通常的RAM相比,cache也是一种存储器,其特点存储速度非常快,但同样容量的cache比同样的RAM成本高了很多,因此不能做的太大。cache亦采用多级存储结构,通常的CPU有8-64K的一级cache,256K-12M的2级cache,有的CPU还有3级cache.许多现代的CPU的2级cache与CPU同频,因此速度非常快。
按照intel的说法,在通常情况下,对RAM的访问实际上近似等同于对cache的访问,因为在90%以上的情况下,RAM中的数据已经被缓冲到cache. 因为程序对RAM的访问具有局部性,也就是说,下次访问的数据的地址和这次访问的数据的地址相邻,所有当访问d[ i ]时,CPU的后台"程序"自动把和d[ i ]相邻的数据载入cache. 下次访问d[ i+1 ]时,可直接从cache中读写数据. 只用当访问的数据不在cache中,CPU才直接从RAM读取数据,由于RAM的很高的延时,故直接从RAM中读写数据比从cache中读写数据要慢得多。
显然,对数据的顺序访问具有极高的cache命中率,因此对数据的顺序访问的性能几乎可达到内存带宽这个上限。内存带宽指内存和cache间批量数据传递的速率,注意内存带宽和内存延时无必然的联系,因为通常情况下,CPU在后台做内存和cache间数据交换,所有内存的高延时被隐藏了。
反之,对数据的随机访问则使得cache命中率大大降低,一旦cache未命中,CPU不得不与内存之间交换数据,此时,内存的高延时就成为瓶颈。
楼下的代码分别测试 对1M*4,4M*4,16M*4,64M*4的内存块进行顺序读取和随机读取所耗费的时间。其速度比如下(测试机的L2 cache is 6M)
块大小速度比
4M 3.0
16M 7.1
64M 11.2
256M 12.5
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include "currTime.h"
typedef unsigned long DWORD;
void initAccessTable(int accessTab[], int len, int isRand )
{
int i,idx;
if (isRand==0)//sequential access
{
for (i=0;i<len;i++)
{
accessTab=i;
}
}
else
{
char *tmpBuff=(char *)malloc(len);
memset(tmpBuff,0,len);
for (i=0;i<len;)
{
idx= ((rand() << 15)+ rand()) % len;
if ( tmpBuff==0)
{
accessTab=idx;
tmpBuff=1;
}
}
free(tmpBuff);
}
}
int sum(DWORD pBuff[], int accessTab[], int len)
{
DWORD s=0;
int i,idx;
for (i=0;i<len;i++)
{
idx=accessTab;
s += pBuff;
}
return s;
}
//mode=0, sequential access
//mode=1, random access
void test_access_RAM(int blk_len, int mode)
{
DWORD *pBuff=malloc(sizeof(DWORD)*blk_len);
int *accessTab=malloc(sizeof(int)*blk_len);
DWORD i,s;
double t;
for (i=0;i<blk_len;i++)
{
pBuff=rand() & 15;
}
initAccessTable(accessTab, blk_len, mode);
t=currTime();
s=sum(pBuff,accessTab,blk_len);
t=currTime()-t;
printf("s=%d\n",s);
if ( mode ==0)
printf("sequential access %d number, it take %.6f ms\n",blk_len,t*1000 );
else
printf("random access %d number, it take %.6f ms\n",blk_len,t*1000 );
free(pBuff);
free(accessTab);
}
void test()
{
int blk_len;
int i,loop;
loop=3;
blk_len=1024*1024;
for (i=0;i<loop;i++)
{ test_access_RAM(blk_len,0); }
for (i=0;i<loop;i++)
{ test_access_RAM(blk_len,1); }
//----------------------------
blk_len=4*1024*1024;
for (i=0;i<loop;i++)
{ test_access_RAM(blk_len,0); }
for (i=0;i<loop;i++)
{ test_access_RAM(blk_len,1); }
//----------------------------
blk_len=16*1024*1024;
for (i=0;i<loop;i++)
{ test_access_RAM(blk_len,0); }
for (i=0;i<loop;i++)
{ test_access_RAM(blk_len,1); }
//----------------------------
blk_len=64*1024*1024;
for (i=0;i<loop;i++)
{ test_access_RAM(blk_len,0); }
for (i=0;i<loop;i++)
{ test_access_RAM(blk_len,1); }
}
int main(int argc, char* argv[])
{
test();
return 0;
}
的确,random access 现在已经不能简单认为是O(1)了,但是细化的研究还是比较欠缺,做cache的还是做编译器的比较多,系统里面大家对cache的认识都比较简化。
最近在思索如果提供给programmer在cache-aware 的programming model,但又不至于增加复杂度
页:
[1]