web-dev-qa-db-ja.com

C ++でのベクトルストレージ

私は、d次元の点の大きなベクトル(dが固定され、小さい:<10)を格納したいと考えています。

Pointvector<int>として定義すると、vector<Point>は各位置にPointへのポインターを格納すると思います。

しかし、Pointstd::Tuple<int,int,...,int>またはstd::array<int, d>のような固定サイズのオブジェクトとして定義すると、プログラムはすべてのポイントを連続メモリに格納するか、それとも間接的な追加レベルが残るのでしょうか?

答えが配列が追加の間接参照を回避することである場合、これはvector<Point>?のスキャン中にパフォーマンス(キャッシュエクスプロイトの局所性)に大きな影響を与える可能性があります。

39
Joseph Stack

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

一方、Pointvector<int>として定義すると、vector<Point>のレイアウトはvector<vector<int>>となりますnot連続、vectorは動的に割り当てられたメモリにpointersを格納するため、したがって、singlePointsには連続性がありますが、構造全体にはありません。

最初のソリューションは2番目のソリューションよりもはるかに効率的です(最近のCPUは連続したメモリ位置へのアクセスを好むため)。

52
Mr.C64

vectorは、タイプに含まれるものを連続したメモリに格納します。したがって、はい、それがarrayまたはTuple、あるいはおそらくさらに優れたカスタム型である場合、間接参照は回避されます。

パフォーマンスに関しては、いつものように、それを測定する必要があります。憶測しないでください。少なくともスキャンに関しては。

ただし、最初にこれらのポイントを作成すると、ポイントを格納するすべてのvectorに不要なメモリ割り当てを回避できるため、パフォーマンスが大幅に向上します。また、メモリ割り当ては通常、C++では非常に負荷がかかります。

7
Sergei Tachenov

上記のd(<10)の値の場合、Pointvector<int>として定義すると、メモリ使用量がstd::vector<Point>のほぼ2倍になり、ほとんどメリットがありません。

4
Leon

ディメンションが固定されているので、ディメンションをテンプレートパラメータとして使用するテンプレートを使用することをお勧めします。このようなもの:

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<ndpoint<...>>最高のパフォーマンスを得るためのポイントのコレクション。

1

データの構造を100%確実にする唯一の方法は、独自のメモリ処理を完全に実装することです。

ただし、チェックアウトできる行列と行列演算を実装する多くのライブラリがあります。連続したメモリ、再形成な​​どに関する情報を文書化しているものもあります(OpenCV Matなど)。

一般に、arrayのポイントが連続しているとは信頼できないことに注意してください。これは、アラインメント、アロケーションブロックヘッダーなどが原因です。たとえば、

struct Point {
   char x,y,z;
};

Point array_of_points[3];

ここで、「形状を変更」しようとすると、つまり、ポイントがコンテナ内で隣接しているという事実を中継して、失敗する可能性が最も高いポイント要素間で反復します。

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