【问题标题】:4 dimensional array C++4维数组C++
【发布时间】:2021-08-12 01:58:09
【问题描述】:

我使用的是 4d 向量,但是使用向量的初始化很长:

vec = std::vector<std::vector<std::vector<std::vector<T>>>>(A, std::vector<std::vector<std::vector<T>>>(B, std::vector<std::vector<T>>(C, std::vector<T>(D, def))));

有什么方法可以轻松缩短它吗?我不需要使用向量,任何 4D 数组都可以,只要我可以在一个地方声明它并在另一个地方初始化它。我也不需要扩展它,但我事先不知道尺寸。

我可以使用 typedef 来创建 vec1d&lt;T&gt; = std::vector&lt;T&gt;vec2d&lt;T&gt; = std::vector&lt;vec1d&lt;T&gt;&gt; = std::vector&lt;std::vector&lt;T&gt;&gt; 等,但这似乎没有必要。我认为可以使用可变参数模板来做一些事情(但我对它们的了解不够:P)。

我当然可以创建另一个类,但如果有更好的方法,我宁愿使用它。

【问题讨论】:

  • 使它成为一个平面的一维数组,并在其上添加一个索引层。这里有很多关于如何做到这一点的例子。
  • 你可以使用 typedefs 如果你问的是typedef std::vector&lt;std::vector&lt;std::vector&lt;std::vector&lt;T&gt;&gt;&gt;&gt; longvector
  • @Nina 这将有助于在数据之上添加一个相当厚的层 - 即使这是 OP 要求的,我建议不要这样做。
  • 你可能对这个thread感兴趣。

标签: c++


【解决方案1】:

由于缓存局部性差以及到达洋葱最后一层所需的中间指针取消引用的延迟,即使是 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-07-27
    • 1970-01-01
    • 2013-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-06
    • 1970-01-01
    相关资源
    最近更新 更多