【问题标题】:Vector storage in C++C++ 中的向量存储
【发布时间】:2017-03-11 05:09:39
【问题描述】:

我希望存储一个大的 d 维点向量(d 固定和小:

如果我将Point 定义为vector<int>,我认为vector<Point> 将在每个位置存储一个指向Point 的指针。

但是如果将Point 定义为固定大小的对象,例如: std::tuple<int,int,...,int>std::array<int, d>, 程序会将所有点存储在连续内存中还是会保留额外的间接级别?

如果答案是数组避免了额外的间接性,这是否会对扫描 vector<Point> 时的性能(缓存利用局部性)产生很大影响?

【问题讨论】:

  • 标准向量类应该在很大程度上与数组兼容,这意味着它分配的数据存储在连续的内存块中,就像数组一样。如果您有一个std::vector<Point>,那么所有Point 对象都将连续存储。如果Point 类本身有指针(直接或间接(比如当它有一个向量时)),那么并非所有数据都会连续存储。
  • 是的,我会走元组路线或更好,只需使用普通的 C 风格结构(我仍然觉得元组使用起来很烦人,即 std::get() 并不是全部直观)。

标签: c++ memory stdvector


【解决方案1】:

如果您将Point 定义为具有连续的数据存储(例如struct Point { int a; int b; int c; } 或使用std::array),那么std::vector<Point> 会将Points 存储在连续的内存位置,因此您的内存布局将是:

p0.a, p0.b, p0.c, p1.a, p1.b, p1.c, ..., p(N-1).a, p(N-1).b, p(N-1).c

另一方面,如果您将Point 定义为vector<int>,则vector<Point> 具有vector<vector<int>> 的布局,这是连续的,因为vector 存储指针 指向动态分配的内存。所以你有 single Points 的连续性,但不是整个结构。

第一种解决方案比第二种更有效(因为现代 CPU 喜欢访问连续的内存位置)。

【讨论】:

  • 一个是否比另一个更有效取决于用例。如果您需要插入一些数据,那么只移动一些指针可能比复制所有数据要快得多。
  • 一般来说,测量是一件好事。但是,具有缓存友好的结构往往对性能很有好处。基于节点的结构往往不利于性能,因为它们对缓存不友好。请注意,分页的成本很高。相反,现代 CPU 喜欢访问 连续 内存位置(这让预取器很高兴)。
【解决方案2】:

对于d (Point 定义为vector<int> 将使std::vector<Point> 的全部内存使用量几乎翻倍,并且几乎不会带来任何优势。

【讨论】:

    【解决方案3】:

    vector 会将您的类型包含的任何内容存储在连续内存中。所以是的,如果那是 arraytuple,或者可能更好的自定义类型,它将避免间接。

    在性能方面,一如既往,您必须对其进行衡量。不要猜测。至少就扫描而言。

    但是,当您首先创建这些点时,肯定会有巨大的性能提升,因为您将避免为每个存储点的vector 分配不必要的内存。在 C++ 中,内存分配通常非常昂贵。

    【讨论】:

      【解决方案4】:

      由于尺寸是固定的,我建议您使用将尺寸用作模板参数的模板。像这样的:

      template <typename R, std::size_t N> class ndpoint 
      {
      public:
        using elem_t=
          typename std::enable_if<std::is_arithmetic<R>::value, R>::type;
      
        static constexpr std::size_t DIM=N;
      
        ndpoint() = default;
      
        // e.g. for copying from a tuple
        template <typename... coordt> ndpoint(coordt... x) : elems_ {static_cast<R>(x)...} {
        }
        ndpoint(const ndpoint& other) : elems_() {
          *this=other;
        }
      
        template <typename PointType> ndpoint(const PointType& other) : elems_() {
          *this = other;
        }
      
        ndpoint& operator=(const ndpoint& other) {
          for(size_t i=0; i<N; i++) {
            this->elems_[i]=other.elems_[i];
          }
          return *this;
        }
      
        // this will allow you to assign from any source which defines the
        // [](size_t i) operator
        template <typename PointT> ndpoint& operator=(const PointT& other) {
          for(size_t i=0; i<N; i++) {
            this->elems_[i]=static_cast<R>(other[i]);
          }
        }
      
        const R& operator[](std::size_t i) const { return this->elems_[i]; }
      
        R& operator[](std::size_t i) { return this->elems_[i]; }
      
      private:
        R elems_[N];
      };
      

      然后使用std::vector&lt;ndpoint&lt;...&gt;&gt; 来收集积分以获得最佳性能。

      【讨论】:

      • 这与 std::vector<:array>> 相比有何不同/更好?
      • "这比 std::vector<:array>> 好在哪里?"控制您可以对 ndpoint 执行的操作(例如,添加 norm 方法或 distanceTo 等)。在性能方面,它是一样的。只是不要使用元组或向量作为 nd 点的存储。
      【解决方案5】:

      100% 确定数据结构的唯一方法是完全实现自己的内存处理。

      但是,您可以查看许多实现矩阵和矩阵运算的库。有些已经记录了有关连续内存、重塑等的信息(例如 OpenCV Mat)。

      请注意,通常您不能相信 array 点是连续的。这是由于对齐,分配块头等。例如考虑

      struct Point {
         char x,y,z;
      };
      
      Point array_of_points[3];
      

      现在,如果您尝试“重塑”,即根据容器中的点相邻这一事实在 Point 元素之间进行迭代,那么它最有可能失败:

      (char *)(&array_of_points[0].z) != (char *)(&array_of_points[1].x)
      

      【讨论】:

        猜你喜欢
        • 2020-12-01
        • 1970-01-01
        • 2012-09-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-17
        • 1970-01-01
        相关资源
        最近更新 更多