【发布时间】:2016-03-17 22:55:11
【问题描述】:
我想在内存中存储一个 3d 卷。为此,我使用线性数组,然后从 3d-index 计算 1d-index。它封装在一个名为Volume 的类中,该类提供了访问数组数据元素的函数。这是访问卷的一个数据元素的函数:
template<typename T>
inline T& Volume<T>::at(size_t x, size_t y, size_t z) {
if (x >= this->xMax || y >= this->yMax || z >= this->zMax) throw std::out_of_range("Volume index out of bounds");
return this->volume[x * this->yMax*this->zMax + y*this->zMax + z]
}
现在,这会以 Z 最快的索引顺序线性化 3d 体积。如果在这样的循环中访问卷,则会在卷元素位于内存中时按顺序对其进行迭代:
Volume<float> volume(10, 20, 30); //parameters define size
for(int x = 0; x < volume.xSize(); ++x) {
for(int y = 0; y < volume.ySize(); ++y) {
for int z = 0; z < volume.zSize(); ++z) {
volume.at(x, y, z); //do sth with this voxel
}
}
}
但是,如果我这样编写循环,它们将不会按顺序访问,而是以更“随机”的顺序访问:
Volume<float> volume(10, 20, 30); //parameters define size
for(int z = 0; z < volume.zSize(); ++z) {
for(int y = 0; y < volume.ySize(); ++y) {
(for int x = 0; x < volume.zSize(); ++x) {
volume.at(x, y, z); //do sth with this voxel
}
}
}
现在,第一种情况运行得很快,第二种情况运行得很慢。我的第一个问题是:为什么?我猜这与缓存有关,但我不确定。
现在,我可以像这样重写卷元素的访问函数:
template<typename T>
inline T& Volume<T>::at(size_t x, size_t y, size_t z) {
if (x >= this->xMax || y >= this->yMax || z >= this->zMax) throw std::out_of_range("Volume index out of bounds");
return this->volume[x * this->yMax*this->zMax + y*this->zMax + z]
}
那么循环顺序 #2 会很快(因为访问是按顺序进行的),但循环顺序 #1 很慢。
现在,由于某种原因,我的程序中需要两个索引顺序。两者都应该很快。这个想法是可以在创建卷时定义索引排序,然后使用该索引排序。首先,我在 at 函数中尝试了一个简单的 if-else 语句。然而,这似乎并没有奏效。
所以我在设置订购模式时尝试了这样的事情:
template<typename T>
void Volume<T>::setMemoryLayout(IndexOrder indexOrder) {
this->mode = indexOrder;
if (indexOrder == IndexOrder::X_FASTEST) {
this->accessVoxel = [this](size_t x, size_t y, size_t z)->T& {
return this->volume[z * this->yMax*this->xMax + y*this->xMax + x];
};
} else {
this->accessVoxel = [this](size_t x, size_t y, size_t z)->T& {
return this->volume[x * this->yMax* this->zMax + y*this->zMax + z];
};
}
}
然后当实际访问体素时:
template<typename T>
inline T& Volume<T>::at(size_t x, size_t y, size_t z) {
if (x >= this->xMax || y >= this->yMax || z >= this->zMax) throw std::out_of_range("Volume index out of bounds");
return this->accessVoxel(x, y, z);
}
所以我的想法是通过在当前模式更改时动态定义一次 lambda 函数来减少 at 函数内部必需的 if 语句的开销。只有在调用at 时才需要调用它。然而,这并没有达到我想要的效果。
我的问题是为什么我的尝试没有奏效,如果有一种方法可以让我真正做我想做的事:支持 X-fastest 和 Y-fastest 索引排序并提供相应的性能提升的卷当相应地循环时。
注意:我的目标是不能在两种模式之间切换,同时有数据分配给卷,数据仍然被正确读取。
【问题讨论】:
-
你在缓存中调用它。一旦你开始跳来跳去,你就不会得到可以批量读取的长时间运行的数据。 CPU 不仅会读取你想要的内存,还会读取一些它的邻居,假设如果你想要 N,那么很快你就会想要 N+1。还不如现在加载 N+1 并在以后节省时间。如果你想要 N 然后 N+100,你将读取 N 周围的内存,然后读取 N+100 周围的内存,这会很痛苦。
-
你的
volume3D 向量真的那么小,还是更大? -
“这没有达到我想要的”不是一个问题。你说的是快,但什么对你来说足够快?我猜你的代码中有一些错误,例如
for int x = 0; x < volume.zSize(); ++x。是的,您可以在编译时指定诸如 row_major 或 column_major 之类的存储模式,例如通过专业化。 -
一个测试编译时常量的
if将优化到几乎没有,就像它从未存在过一样。当编译器无法证明accessVoxel被设置为什么时,评估 lamba 表达式可能不会被优化掉。就像@knivil 所说,将行主要与列主要存储顺序作为模板参数应该可以很好地解决问题。您总是希望此类的函数内联,因此无需生成更多独立版本的函数。 -
stackoverflow.com/questions/12264970/… 有一个答案,其中包含指向其他问题的一些链接,因此,如果您想开始阅读大量有关顺序访问与跨步访问的文本,以及跨步为 2048B 的倍数时的潜在问题(缓存别名)/ 4096B(英特尔访问之间的错误依赖关系),然后开始寻找那里。
标签: c++ arrays performance