C++ C++;:按顺序访问元素时,访问C阵列的速度要快得多

C++ C++;:按顺序访问元素时,访问C阵列的速度要快得多,c++,arrays,performance,C++,Arrays,Performance,我想在内存中存储一个3d卷。为此,我使用了一个线性阵列,然后从3d索引计算出1d索引。它封装在一个名为Volume的类中,该类提供了访问数组数据元素的函数。以下是访问卷的一个数据元素的函数: 模板 内联T&Volume::at(大小x、大小y、大小z){ 如果(x>=this->xMax | | y>=this->yMax | z>=this->zMax)将std::抛出范围之外(“卷索引超出范围”); 返回此->卷[x*此->yMax*此->zMax+y*此->zMax+z] } 现在,这将

我想在内存中存储一个3d卷。为此,我使用了一个线性阵列,然后从3d索引计算出1d索引。它封装在一个名为
Volume
的类中,该类提供了访问数组数据元素的函数。以下是访问卷的一个数据元素的函数:

模板
内联T&Volume::at(大小x、大小y、大小z){
如果(x>=this->xMax | | y>=this->yMax | z>=this->zMax)将std::抛出范围之外(“卷索引超出范围”);
返回此->卷[x*此->yMax*此->zMax+y*此->zMax+z]
}
现在,这将以Z-最快索引顺序线性化3d卷。如果在这样的循环中访问卷,则会在内存中的卷元素上按顺序进行迭代:

卷(10,20,30)//参数定义大小
对于(int x=0;x
但是,如果我这样编写循环,它们将不会按顺序访问,而是以更“随机”的顺序访问:

卷(10,20,30)//参数定义大小
对于(int z=0;z
现在,第一个案例运行得很快,第二个案例运行得很慢。我的第一个问题是:为什么?我想这与缓存有关,但我不确定

现在,我可以像这样重写volume元素的访问函数:

模板
内联T&Volume::at(大小x、大小y、大小z){
如果(x>=this->xMax | | y>=this->yMax | z>=this->zMax)将std::抛出范围之外(“卷索引超出范围”);
返回此->卷[x*此->yMax*此->zMax+y*此->zMax+z]
}
然后循环顺序#2会很快(因为访问是按顺序进行的),但循环顺序#1会很慢

现在,出于某种原因,我需要在我的程序中使用这两个索引顺序。两者都应该很快。其思想是,在创建卷时,可以定义索引顺序,然后使用该索引顺序。首先,我在
at
函数中尝试了一个简单的if-else语句。然而,这似乎并不奏效

因此,在设置订购模式时,我尝试了以下方法:

模板
无效卷::setMemoryLayout(IndexOrder IndexOrder){
此->模式=索引顺序;
if(indexOrder==indexOrder::X_){
this->accessVoxel=[this](大小x,大小y,大小z)->t&{
返回此->卷[z*此->yMax*此->xMax+y*此->xMax+x];
};
}否则{
this->accessVoxel=[this](大小x,大小y,大小z)->t&{
返回此->卷[x*此->yMax*此->zMax+y*此->zMax+z];
};
}
}
然后当实际访问体素时:

模板
内联T&Volume::at(大小x、大小y、大小z){
如果(x>=this->xMax | | y>=this->yMax | z>=this->zMax)将std::抛出范围之外(“卷索引超出范围”);
返回此->访问体素(x,y,z);
}

因此,我的想法是通过在当前模式更改时动态定义lambda函数一次,来减少if语句的开销,这在
at
函数中是必要的。然后,只有在调用
at
时才需要调用它。然而,这并没有达到我的期望

我的问题是为什么我的尝试没有成功,如果有一种方法我可以真正做到我想要的:一个支持X-fast和Y-fast索引排序的卷,并且在相应地循环时提供相应的性能增益


注意:我的目标是在有数据分配给卷且数据仍在正确读取的情况下,无法在两种模式之间切换

计算机的物理内存不够大,无法容纳整个阵列。解决问题的一个方法是增加更多内存

您的操作系统可以使用虚拟内存。每当需要更多内存时,虚拟内存页将被移动到磁盘。访问磁盘非常耗时,这会降低性能。在更糟糕的情况下,操作系统一直在写和读(或只是读)页面。因此,另一种解决方案是以这样一种方式重新组织数据,即无论扫描像素的方向如何,磁盘访问都大致相同。我建议使用一个页面大小的3D区域(通常是4KB,所以是一个16像素大小的立方体)。因此,当你朝一个方向扫描时,你只会接触其中的几页,而当你朝另一个方向扫描时,你会接触相同数量的不同页面。如果运气好一点(取决于可用的物理内存),就不会有页面不必要地进出交换文件

最好和最简单的解决方案是只在一个方向上扫描像素。也许你真的不必具备横向扫描像素的能力。

在我的cpu(可能还有你的)上,我有64字节的缓存线。每个缓存线包含16个4字节浮点。当为第一个浮点数提取缓存线时,在顺序访问时,不需要为接下来的15个浮点数重复该工作

请注意,从主内存获取缓存线大约需要240个周期。从一级缓存中获取数据大约需要12个周期,如果您可以重复命中一级缓存,这将是一个很大的区别。(L2成本约为40个周期,L3成本约为150个周期)

顺序访问带来的第二个缓存优势是,在顺序读取数据时,CPU会将数据预取到缓存中。因此,如果从数组的开头开始并按顺序在其中移动,甚至可以避免在中读取缓存线的惩罚

L1