簡體   English   中英

C ++ Parallel std :: vector使用昂貴的復制進行排序

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

假設我有一個vector<int> intVec和一個vector<vector<double> > matrix 我想對intVec進行排序,並在C ++中相應地重新排序matrix的第一維。 我之前已經多次問過這個問題,但是這個案子有一個轉折點。 vector<double>復制起來很昂貴,因此例如將intVecmatrix都復制到vector<pair<int, vector<double> >intVec進行排序並將其復制回來的效率甚至比平時低。

intVec滾動自己的自定義排序算法之外,如何在不復制matrix任何元素和調用vector的復制構造函數的情況下intVec進行排序並對鎖步的matrix的第一維重新排序?

vector<double>復制起來很昂貴,因此例如將intVec和matrix都復制到vector<pair<int, vector<double> > ,對它進行排序並將其復制回來的效率甚至比平常低。

獲得所需優化的最簡單方法是將源vector<vector<double>>的元素交換到臨時vector<pair<int, vector<double>> ,對其進行排序,然后它們交換回原始載體中的新位置。

仍然會有超出嚴格必要的開銷(例如構造和銷毀空向量)。 但是,沒有復制任何向量,代碼仍然與您已有的非常相似。 所以,如果你的問題是復制成本是正確的,問題就解決了。

在C ++ 11中,您可以向兩個方向移動而不是交換。 我懷疑使用空向量移動和交換之間有很多性能差異,但我不確定沒有。

基於之間的空間你的兩個>的,我猜你正在使用預C ++ 11 C ++。 在C ++ 11中, std::sort似乎盡可能地移動元素而不是復制。

您可以將自定義比較器傳遞給std::sort 但是,即使你這樣做,你也要做pair<int, vector<double> > >>>的Theta(n log n)副本。

我猜,基於沒有實際嘗試它,你應該排序一pair<int, vector<double> *> (或者如果int足夠大,則pair<int, int> ),使用第二個int作為索引)相反,要獲得適當的排列,然后使用vectorswap成員函數應用置換,以避免復制向量內容。

一個選項:創建一個std::vector<std::pair<int,size_t>> ,其中第一個元素是intVec中的int,第二個元素是該元素的原始索引。 然后對新矢量進行排序。 然后將您的矩陣和intVec洗牌到對的第二個元素所指示的順序(例如,通過單次傳遞,進行交換)。

如果您不想復制vector<double>項向量,則將指針或索引的vector<double>vector<double>項。 將其與主矢量一起排序。

然而,你並不清楚你會獲得性能提升,因此我建議你測量直接排序和智能排序,並進行比較。


例:

#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;
    }
}

顯而易見的答案是將兩個向量重構為一個vector<pair<int, vector<double> > (因為數據顯然是緊密耦合的)。

如果那不是一個選項,那么創建另一個索引向量並排序而不是vec和矩陣。

由於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}}
}

我很確定 - 當你使用一些最新的編譯器時(比如gcc 4.4及更高版本)---沒有真正復制的東西:現在C ++標准庫容器中的對象(大多數)總是被移動。 因此恕我直言,沒有必要擔心昂貴的副本。

看看下面的例子 - 它是在Debian下使用gcc 4.4.6編寫的。 正如您所看到的,在“重新排序”階段,沒有調用復制構造函數,也沒有調用`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;
}

輸出:

[               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