由于缓存局部性差以及到达洋葱最后一层所需的中间指针取消引用的延迟,即使是 2 层深的向量向量通常也是一个坏主意。
所以,最普遍的情况是:您只需要一个向量。无需多次分配。毕竟尺寸是固定的,所以一次分配就可以了。
下一个问题是在其中安排数据以实现缓存友好的最佳方案。一个简单的“行”主要顺序(即相邻元素位于最高维度)可能是一个好的开始 - 如下面的简单示例所示。
如果有任何访问模式沿任何维度读取连续元素但最后一个,则需要更好的索引方案来确保所有四个维度中的相邻元素保持“在一起”: [i,0,0,0], [i,0,0,1], [i,0,1,0], [i,0,1,1], [i,1,0,0], [i,1,0,1], [i,1,1,0], [i,1,1,1], [i+1,0,0,0],等等。
当然,缓存不是万能的。现代 CPU 上的预取器能够找出大多数访问模式,但缓存未命中仍然会限制带宽,因为在最坏的情况下,每个元素访问都需要获取整个缓存行,而之前大部分行都没有用它被驱逐。这可能是也可能不是问题。如果元素的大小是一个高速缓存行(64 字节,包括对齐填充),这不是问题。然后,如果数组很大(跨越许多内存页面),您将发生页表 TLB 未命中,以及一般的页面未命中。
如果元素很小,使用编译器内在函数来利用 CPU 的向量操作可能是有意义的,例如同时操作 512 位数据(非常适合 floats 或 ints 的 2x2x2x2 立方体)。
有许多更好的安排可供选择,您可以根据访问模式的统计属性和/或用于处理数据的算法来选择一种。比如说,多维 FFT 可以通过定制的索引方案做得很好。
这个想法是让尽可能多的连续访问的元素进入缓存行。如果数组很小但始终沿不同维度连续访问,则保留 4 个副本可能有意义,每个副本具有由不同维度索引的相邻元素。如果它更小——以至于它只能部分填充 L1 D-cache(比如总大小小于 1kb)——这样的副本将是有害的。不过,只有您确切地知道这里需要什么。请将该信息添加到问题中:)
#include <array>
#include <cassert>
#include <cstddef>
#include <type_traits>
#include <vector>
template <class T>
class array_4d {
std::vector<T> _data;
const std::array<size_t, 4> _n;
const std::array<size_t, 4> _nn;
using const_value_type =
std::conditional_t<std::is_scalar_v<T>, const T, const T &>;
public:
using value_type = T;
using reference = T&;
array_4d(size_t n1, size_t n2, size_t n3, size_t n4, T &&_default= {}) :
_data(n1*n2*n3*n4, std::move(_default)),
_n{n1, n2, n3, n4},
_nn{n2*n3*n4, n3*n4, n4, 1}
{}
const_value_type operator()(size_t i, size_t j, size_t k, size_t l) const {
return _data[_nn[0]*i + _nn[1]*j + _nn[2]*k + _nn[3]*l];
}
reference operator() (size_t i, size_t j, size_t k, size_t l) noexcept {
return _data[_nn[0]*i + _nn[1]*j + _nn[2]*k + _nn[3]*l];
}
size_t size() const noexcept { return _data.size(); }
size_t size(int dim) const noexcept { return _n[dim]; }
struct _jkl {
T* const arr; size_t const nj, nk;
struct _kl {
T* const arr; size_t const nk;
struct _l {
T* const arr;
reference operator[](size_t l) noexcept { return arr[l]; }
};
_l operator[](size_t k) noexcept { return { arr + nk*k }; }
};
_kl operator[](size_t j) noexcept { return { arr + nj*j, nk }; }
};
_jkl operator[](size_t i) { return { &_data[_nn[0]*i], _nn[1], _nn[2] }; }
};
int main()
{
// usage example
array_4d<int> A(4,4,4,4);
A(0,1,2,3) = 42;
assert(A[0][1][2][3] == 42);
}
您可以在 Godbolt 上使用此代码:https://godbolt.org/z/joG6x15r9