简体   繁体   English

C ++ Parallel std :: vector使用昂贵的复制进行排序

[英]C++ Parallel std::vector Sorting With Expensive Copying

Assume I have a vector<int> intVec and a vector<vector<double> > matrix . 假设我有一个vector<int> intVec和一个vector<vector<double> > matrix I want to sort intVec and reorder the first dimension of matrix correspondingly in C++. 我想对intVec进行排序,并在C ++中相应地重新排序matrix的第一维。 I realize this question has been asked several times before, but this case has a twist. 我之前已经多次问过这个问题,但是这个案子有一个转折点。 A vector<double> is expensive to copy, so eg copying both intVec and matrix to a vector<pair<int, vector<double> > , sorting that and copying them back is even more inefficient than usual. vector<double>复制起来很昂贵,因此例如将intVecmatrix都复制到vector<pair<int, vector<double> >intVec进行排序并将其复制回来的效率甚至比平时低。

Short of rolling my own custom sorting algorithm, how can I sort intVec and reorder the first dimension of matrix in lockstep without copying any element of matrix and invoking vector 's copy constructor ? intVec滚动自己的自定义排序算法之外,如何在不复制matrix任何元素和调用vector的复制构造函数的情况下intVec进行排序并对锁步的matrix的第一维重新排序?

A vector<double> is expensive to copy, so eg copying both intVec and matrix to a vector<pair<int, vector<double> > , sorting that and copying them back is even more inefficient than usual. vector<double>复制起来很昂贵,因此例如将intVec和matrix都复制到vector<pair<int, vector<double> > ,对它进行排序并将其复制回来的效率甚至比平常低。

The simplest way to get the optimization you want then is to swap the elements of the source vector<vector<double>> into the temporary vector<pair<int, vector<double>> , sort it, and then swap them back to their new locations in the original vector. 获得所需优化的最简单方法是将源vector<vector<double>>的元素交换到临时vector<pair<int, vector<double>> ,对其进行排序,然后它们交换回原始载体中的新位置。

There will still be more overhead than is strictly necessary (for example to construct and destroy empty vectors). 仍然会有超出严格必要的开销(例如构造和销毁空向量)。 However, no vector is ever copied and the code remains very similar to what you already have. 但是,没有复制任何向量,代码仍然与您已有的非常相似。 So if you are correct that the problem is the cost of copying, problem solved. 所以,如果你的问题是复制成本是正确的,问题就解决了。

In C++11 you could move in both directions rather than swapping. 在C ++ 11中,您可以向两个方向移动而不是交换。 I doubt there's much performance difference between moving and swapping with an empty vector, but I'm not sure there isn't. 我怀疑使用空向量移动和交换之间有很多性能差异,但我不确定没有。

Based on the space between your two > 's, I'm guessing you're using pre-C++11 C++. 基于之间的空间你的两个>的,我猜你正在使用预C ++ 11 C ++。 In C++11, std::sort seems to move elements whenever possible instead of copying. 在C ++ 11中, std::sort似乎尽可能地移动元素而不是复制。

You can pass a custom comparator to std::sort . 您可以将自定义比较器传递给std::sort However, even if you do this, you're doing Theta(n log n) copies of pair<int, vector<double> > 's. 但是,即使你这样做,你也要做pair<int, vector<double> > >>>的Theta(n log n)副本。

I'd guess, based on not actually trying it, that you should sort a pair<int, vector<double> *> (or pair<int, int> if int is big enough), using the second int as an index) instead to get the appropriate permutation and then apply the permutation using vector 's swap member function to avoid copying the vector contents. 我猜,基于没有实际尝试它,你应该排序一pair<int, vector<double> *> (或者如果int足够大,则pair<int, int> ),使用第二个int作为索引)相反,要获得适当的排列,然后使用vectorswap成员函数应用置换,以避免复制向量内容。

One option: create a std::vector<std::pair<int,size_t>> where the first element is the int from intVec and the second element is the original index of that element. 一个选项:创建一个std::vector<std::pair<int,size_t>> ,其中第一个元素是intVec中的int,第二个元素是该元素的原始索引。 Then sort that new vector. 然后对新矢量进行排序。 Then shuffle your matrix and intVec to the order indicated by the second elements of the pairs (eg, with a single pass, doing swaps). 然后将您的矩阵和intVec洗牌到对的第二个元素所指示的顺序(例如,通过单次传递,进行交换)。

If you don't want to copy the vector<double> item vector, then make a vector of pointer or indices to the vector<double> items. 如果您不想复制vector<double>项向量,则将指针或索引的vector<double>vector<double>项。 Sort it along with the main vector. 将其与主矢量一起排序。

It is however not at all clear that you'll get a performance gain, and so I suggest that you measure both the straightforward sorting and the smart sorting, and compare. 然而,你并不清楚你会获得性能提升,因此我建议你测量直接排序和智能排序,并进行比较。


Example: 例:

#include    <algorithm>
#include    <vector>
#include    <iostream>
using namespace std;

struct Mat
{
    vector< vector< double > >  items;

    Mat( int const size )
        : items( size, vector< double >( size ) )
    {}
};

struct KeyAndData
{
    int                     key;
    vector< double > const* data;

    friend bool operator<( KeyAndData const& a, KeyAndData const& b )
    {
        return a.key < b.key;
    }
};

int main()
{
    int const       data[]  = {3, 1, 4, 1, 5};
    Mat             m( 5 );
    vector<int>     v( 5 );

    for( int i = 0;  i < 5;  ++i )
    {
        m.items[i][i] = v[i] = data[i];
    }

    vector< KeyAndData >        sorted( 5 );
    for( int i = 0;  i < 5;  ++i )
    {
        sorted[i].key = v[i];
        sorted[i].data = &m.items[i];
    }

    sort( sorted.begin(), sorted.end() );
    for( int i = 0;  i < 5;  ++i )
    {
        cout << sorted[i].key << ":  ";

        vector< double > const& r = *sorted[i].data;
        for( int x = 0;  x < 5;  ++x )
        {
            cout << r[x] << " ";
        }
        cout << endl;
    }
}

The obvious answer is to restructure your two vectors as one vector<pair<int, vector<double> > (since the data are clearly tightly coupled aready). 显而易见的答案是将两个向量重构为一个vector<pair<int, vector<double> > (因为数据显然是紧密耦合的)。

If that's really not an option then create another vector of indexes and sort that instead of the vec and matrix. 如果那不是一个选项,那么创建另一个索引向量并排序而不是vec和矩阵。

Since std::vector::swap operates in constant time, you could use a sorting algorithm that operates via a series of swaps (like quicksort) to sort intVec while simultaneously performing identical swaps on matrix : 由于std::vector::swap在恒定时间内运行,因此您可以使用通过一系列交换(如intVec )操作的排序算法来对intVec进行排序,同时在matrix上执行相同的交换:

#include <iostream>
#include <vector>
#include <algorithm>

// Sorts intVec in [low, high) while also performing identical swaps on matrix.
void iqsort(std::vector<int> &intVec, std::vector<std::vector<double>> &matrix,
            int low, int high) {
  if (low >= high) return;
  int pivot = intVec[low];
  int nLow = low + 1;
  int nHigh = high - 1;
  while (nLow <= nHigh) {
    if (intVec[nLow] <= pivot) {
      ++nLow;
    } else {
      std::swap(intVec[nLow], intVec[nHigh]);
      std::swap(matrix[nLow], matrix[nHigh]);
      --nHigh;
    }   
  }
  std::swap(intVec[low], intVec[nHigh]);
  std::swap(matrix[low], matrix[nHigh]);

  iqsort(intVec, matrix, low, nHigh);
  iqsort(intVec, matrix, nLow, high);
}

int main() {
  std::vector<int> intVec = {10, 1, 5}; 
  std::vector<std::vector<double>> matrix = {{33.0}, {11.0}, {44.0}};  
  iqsort(intVec, matrix, 0, intVec.size());
  // intVec is {1, 5, 10} and matrix is {{11.0}, {44.0}, {33.0}}
}

I'm pretty sure that --- when you use some up to date compiler (let's say gcc 4.4 and up) --- there nothing really copied: nowadays objects within C++ standard library containers are (mostly) always moved. 我很确定 - 当你使用一些最新的编译器时(比如gcc 4.4及更高版本)---没有真正复制的东西:现在C ++标准库容器中的对象(大多数)总是被移动。 Therefore IMHO there is no need to have any fear about expensive copies. 因此恕我直言,没有必要担心昂贵的副本。

Have a look at the following example - it was written using gcc 4.4.6 under Debian. 看看下面的例子 - 它是在Debian下使用gcc 4.4.6编写的。 As you can see, during the 'reordering' phase, there is no call to the copy constructor and also no call to the `operator=(... & other)'. 正如您所看到的,在“重新排序”阶段,没有调用复制构造函数,也没有调用`operator =(...&other)'。

#include <vector>
#include <iostream>
#include <iomanip>

class VeryExpensiveToCopy {
public:
   explicit VeryExpensiveToCopy(long i) : id(i) { ++cnt_normal_cstr; }

   // Just to be sure this is never used.
   VeryExpensiveToCopy & operator=(VeryExpensiveToCopy & other) = delete;
   VeryExpensiveToCopy(VeryExpensiveToCopy & other) = delete;

   VeryExpensiveToCopy(VeryExpensiveToCopy && other) : id(other.id) {
      ++cnt_move_cstr;
   }
   VeryExpensiveToCopy & operator=(VeryExpensiveToCopy && other) {
      id = other.id; ++cnt_op_as_move; return *this;
   }

   long get_id() const { return id; }

   static void print_stats(std::string const & lref) {
      std::cout << "[" << std::setw(20) << lref << "] Normal Cstr [" 
                << cnt_normal_cstr 
                << "] Move Cstr [" << cnt_move_cstr
                << "] operator=(&&) [" << cnt_op_as_move << "]" << std::endl;
   }

private:
   long id;

   static long cnt_normal_cstr;
   static long cnt_move_cstr;
   static long cnt_op_as_move;
};

// Counts the number of calls.
long VeryExpensiveToCopy::cnt_normal_cstr { 0 };
long VeryExpensiveToCopy::cnt_move_cstr { 0 };
long VeryExpensiveToCopy::cnt_op_as_move { 0 };

int main() {
   std::vector<VeryExpensiveToCopy> v;

   VeryExpensiveToCopy::print_stats("Start");
   for(auto i(0); i<100000; ++i) {
      v.emplace_back(i);
   }
   VeryExpensiveToCopy::print_stats("After initialization");
   for(auto i(0); i<100000-1; ++i) {
      v[i] = std::move(v[i+1]);
   }
   VeryExpensiveToCopy::print_stats("After moving");
   for(auto i(0); i<100000-1; ++i) {
      if(v[i].get_id() != i+1) { abort(); }
   }
   VeryExpensiveToCopy::print_stats("After check");

   return 0;
}

Output: 输出:

[               Start] Normal Cstr [0] Move Cstr [0] operator=(&&) [0]
[After initialization] Normal Cstr [100000] Move Cstr [131071] operator=(&&) [0]
[        After moving] Normal Cstr [100000] Move Cstr [131071] operator=(&&) [99999]
[         After check] Normal Cstr [100000] Move Cstr [131071] operator=(&&) [99999]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM