C++ “a”是什么;“缓存友好”;密码?
“缓存不友好代码”和“缓存友好代码”之间有什么区别 如何确保编写缓存效率高的代码 预备赛 在现代计算机上,只有最低级别的内存结构(寄存器)才能在单个时钟周期内移动数据。然而,寄存器非常昂贵,大多数计算机内核只有不到几十个寄存器。在内存频谱的另一端(DRAM),内存非常便宜(即实际上便宜数百万倍),但在请求接收数据后需要数百个周期。为了弥合超快与昂贵、超慢与廉价之间的差距,我们推出了高速缓存,它以降低速度和成本的方式命名为L1、L2、L3。其思想是,大多数执行代码将经常命中一小部分变量,而其余部分(一个更大的变量集)则很少命中。如果处理器在一级缓存中找不到数据,则在二级缓存中查找。如果不存在,则为三级缓存,如果不存在,则为主内存。每一个“失误”在时间上都是昂贵的 (类似地,缓存就是系统内存,因为系统内存太过硬盘存储。硬盘存储非常便宜,但速度非常慢) 缓存是减少延迟影响的主要方法之一。套用Herb Sutter(cfr.链接如下):增加带宽很容易,但我们无法花钱摆脱延迟 数据总是通过内存层次结构进行检索(最小==从最快到最慢)。缓存命中/未命中通常是指CPU中最高级别的缓存中的命中/未命中——我所说的最高级别是指最大的==最慢的。缓存命中率对性能至关重要,因为每次缓存未命中都会导致从RAM(或更糟的…)获取数据,这需要大量的时间(RAM需要数百个周期,HDD需要数千万个周期)。相比之下,从(最高级别)缓存读取数据通常只需要几个周期 在现代计算机体系结构中,性能瓶颈是离开CPU芯片(例如访问RAM或更高)。随着时间的推移,情况只会变得更糟。处理器频率的增加目前与性能的提高不再相关问题在于内存访问。因此,CPU中的硬件设计工作目前主要集中在优化缓存、预取、管道和并发性上。例如,现代CPU大约85%的内存用于缓存,高达99%的内存用于存储/移动数据 关于这个问题有很多话要说。以下是一些关于缓存、内存层次结构和正确编程的重要参考资料:C++ “a”是什么;“缓存友好”;密码?,c++,performance,caching,memory,cpu-cache,fortran,matlab,c,C++,Performance,Caching,Memory,Cpu Cache,Fortran,Matlab,C,“缓存不友好代码”和“缓存友好代码”之间有什么区别 如何确保编写缓存效率高的代码 预备赛 在现代计算机上,只有最低级别的内存结构(寄存器)才能在单个时钟周期内移动数据。然而,寄存器非常昂贵,大多数计算机内核只有不到几十个寄存器。在内存频谱的另一端(DRAM),内存非常便宜(即实际上便宜数百万倍),但在请求接收数据后需要数百个周期。为了弥合超快与昂贵、超慢与廉价之间的差距,我们推出了高速缓存,它以降低速度和成本的方式命名为L1、L2、L3。其思想是,大多数执行代码将经常命中一小部分变量,而其余部分
- 。在他的优秀文档中,您可以找到涵盖汇编语言到C++语言的详细示例。
- 如果你喜欢视频,我强烈建议你看一看(特别是12:00以后!)
- (索尼科技总监)
- LWN.net的文章
std::vector
与std::list
。std::vector
的元素存储在连续内存中,因此访问它们比访问std::list
中的元素更容易缓存,后者将其内容存储在所有位置。这是由于空间位置
Bjarne Stroustrup在中给出了一个很好的例子(感谢@Mohammad Ali Baydoun的链接!)
在数据结构和算法设计中不要忽视缓存
尽可能地调整数据结构和计算顺序,以最大限度地利用缓存。这方面的一种常见技术是,这在高性能计算(例如cfr)中非常重要
了解并利用数据的隐含结构
另一个简单的例子是存储二维数组的列主顺序(例如,)与行主顺序(例如,)的比较,这一点很多业内人士有时会忘记。例如,考虑下面的矩阵:
1 2
3 4
在行主顺序中,它作为1234
存储在内存中;在列主顺序中,这将存储为1 3 2 4
。很容易看出,不利用这种顺序的实现将很快遇到(很容易避免!)缓存问题。不幸的是,我经常在我的领域(机器学习)看到这样的东西@MatteoItalia在他的回答中更详细地展示了这个例子
当从内存中提取矩阵的某个元素时,它附近的元素也将被提取并存储在缓存线中。如果利用排序,这将导致更少的内存访问(因为接下来的几个值
M[0][0] (memory) + M[0][1] (cached) + M[1][0] (memory) + M[1][1] (cached)
= 1 + 2 + 3 + 4
--> 2 cache hits, 2 memory accesses
M[0][0] (memory) + M[1][0] (memory) + M[0][1] (memory) + M[1][1] (memory)
= 1 + 3 + 2 + 4
--> 0 cache hits, 4 memory accesses
// Cache-friendly version - processes pixels which are adjacent in memory
for(unsigned int y=0; y<height; ++y)
{
for(unsigned int x=0; x<width; ++x)
{
... image[y][x] ...
}
}
// Cache-unfriendly version - jumps around in memory for no good reason
for(unsigned int x=0; x<width; ++x)
{
for(unsigned int y=0; y<height; ++y)
{
... image[y][x] ...
}
}
for(i=0;i<N;i++) {
for(j=0;j<N;j++) {
dest[i][j] = 0;
for( k=0;k<N;k++) {
dest[i][j] += src1[i][k] * src2[k][j];
}
}
}
int itemsPerCacheLine = CacheLineSize / sizeof(elemType);
for(i=0;i<N;i++) {
for(j=0;j<N;j += itemsPerCacheLine ) {
for(jj=0;jj<itemsPerCacheLine; jj+) {
dest[i][j+jj] = 0;
}
for( k=0;k<N;k++) {
for(jj=0;jj<itemsPerCacheLine; jj+) {
dest[i][j+jj] += src1[i][k] * src2[k][j+jj];
}
}
}
}
struct Product
{
int32_t key;
char name[56];
int32_t price'
}
/* create an array of structs */
Product* table = new Product[N];
/* now load this array of structs, from a file etc. */
/* create separate arrays for each attribute */
int32_t* key = new int32_t[N];
char* name = new char[56*N];
int32_t* price = new int32_t[N];
/* now load these arrays, from a file etc. */
SELECT SUM(price)
FROM PRODUCT
int sum = 0;
for (int i=0; i<N; i++)
sum = sum + table[i].price;
int sum = 0;
for (int i=0; i<N; i++)
sum = sum + price[i];