[英]C++ Parallel std::vector Sorting With Expensive Copying
假設我有一個vector<int>
intVec
和一個vector<vector<double> >
matrix
。 我想對intVec
進行排序,並在C ++中相應地重新排序matrix
的第一維。 我之前已經多次問過這個問題,但是這個案子有一個轉折點。 vector<double>
復制起來很昂貴,因此例如將intVec
和matrix
都復制到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
作為索引)相反,要獲得適當的排列,然后使用vector
的swap
成員函數應用置換,以避免復制向量內容。
一個選項:創建一個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.