流程说明
我的代码中相对较慢的部分包括合并两个向量v1
和v2
。 v1
通常比v2
长2到1000倍。 v1
已排序且不包含任何重复项,而v2
未排序且可能包含重复项。
该过程包括使v1
等于我们删除所有重复项的v1
和v2
的合并排序结果。应从所有内容中清除v2
例如,给定输入
v1 = {1,2,5,6,9,10};
v2 = {3,7,5,3}
流程必须输出
v1 = {1,2,3,5,6,7,9,10};
v2 = {};
虽然v1
和v2
的元素不是原始数据,因此移动语义可能会提高性能。
问题
执行此过程的最快方法是什么?
对象类型
我们首先定义这个类。请注意,重复项的排序/删除只能基于属性a
来完成,因为从该类的运算符方法的定义中可以看出它。
class A
{
public:
int a;
int b;
int c;
A(int i1, int i2, int i3):a(i1),b(i2),c(i3){}
bool operator==(const A& other) const
{
return a == other.a;
}
bool operator>(const A& other) const
{
return a > other.a;
}
bool operator<(const A& other) const
{
return a < other.a;
}
};
三种可能的解决方案
我还没有尝试调试以下功能,所以我希望我没有搞砸到这里。
void f1(std::vector<A>& v1, std::vector<A>& v2 )
{
v1.insert(
v1.end(),
make_move_iterator(v2.begin()),
make_move_iterator(v2.end())
);
v2.clear();
std::sort(v1.begin(), v1.end());
v1.erase(
unique(
v1.begin(),
v1.end()
),
v1.end()
);
}
void f2(std::vector<A>& v1, std::vector<A>& v2 )
{
std::sort(v2.begin(), v2.end());
v2.erase(
unique(
v2.begin(),
v2.end()
),
v2.end()
);
v1.insert(
v1.end(),
make_move_iterator(v2.begin()),
make_move_iterator(v2.end())
);
v2.clear();
std::sort(v1.begin(), v1.end());
v1.erase(
unique(
v1.begin(),
v1.end()
),
v1.end()
);
}
void f3(std::vector<A>& v1, std::vector<A>& v2 )
{
std::sort(v2.begin(), v2.end());
std::vector<A> tmp;
std::merge(v1.begin(),v1.end(),v2.begin(),v2.end(),std::back_inserter(tmp));
v1 = std::move(tmp);
v1.erase(
unique(
v1.begin(),
v1.end()
),
v1.end()
);
v2.clear();
}
基准
我对这三个功能进行了基准测试
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <ctime>
void createVector(std::vector<A>& v1, std::vector<A>& v2)
{
int n1 = 10000;
int n2 = 500;
std::mt19937 mt{};
std::uniform_int_distribution<int> dist(0,v1.size() * 100);
for (int i = 0 ; i < n1; i++)
{
v1.push_back({dist(mt),dist(mt),dist(mt)});
}
for (int i = 0 ; i < n2; i++)
{
v2.push_back({dist(mt),dist(mt),dist(mt)});
}
// sort and remove duplicates of v1 only
std::sort(v1.begin(), v1.end());
v1.erase(
unique(
v1.begin(),
v1.end()
),
v1.end()
);
}
std::vector<long double> benchmark ()
{
std::vector<long double> toReturn;
std::vector<A> v1;
std::vector<A> v2;
std::clock_t before;
std::clock_t after;
createVector(v1,v2);
before = clock();
f1(v1,v2);
after = clock();
toReturn.push_back(((long double) after - (long double) before) / (long double) CLOCKS_PER_SEC);
createVector(v1,v2);
before = clock();
f2(v1,v2);
after = clock();
toReturn.push_back(((long double) after - (long double) before) / (long double) CLOCKS_PER_SEC);
createVector(v1,v2);
before = clock();
f3(v1,v2);
after = clock();
toReturn.push_back(((long double) after - (long double) before) / (long double) CLOCKS_PER_SEC);
return toReturn;
}
int main ()
{
int nbRepeats = 500;
std::vector<long double> times(3,0.0);
for (int i = 0 ; i < nbRepeats ;i++)
{
auto b = benchmark();
for (int fun = 0 ; fun < b.size() ;fun++)
times[fun] = b[fun];
}
for (int fun = 0 ; fun < times.size() ;fun++)
std::cout << "f"<< fun + 1 << ": " << times[fun] / nbRepeats << "\n";
return 0;
}
输出
f1: 8e-10
f2: 6e-10
f3: 1.2e-09
有没有更快的解决方案?
答案 0 :(得分:0)
最快的方式将取决于......不可思议的因素。在典型负载下,您需要在实际机器类型上对实际应用程序进行基准测试。
版本f3可以改进,因此它不会扩展v1,这可能会导致重新分配。
using namespace std;
using A = int; // Or whatever
void f4( vector<A>& v1, vector<A>& v2) {
sort(v2.begin(), v2.end());
vector<A> ret(v1.size() + v2.size());
merge(v1.begin(), v1.end(), v2.begin(), v2.end(), ret.begin());
ret.erase( unique( ret.begin(), ret.end() ), ret.end() );
swap(v1, ret);
}
我将从我的个人存储中提交合并例程。它在适当的时候使用移动语义。
namespace dj {
// Move a range of values
template<class Ptr, class Ptr2>
Ptr2 move(Ptr src, Ptr end, Ptr2 dest) {
using value_type = typename std::remove_reference<decltype(*src)>::type;
if constexpr (std::is_trivially_copyable_v<value_type>) {
return std::copy(src, end, dest);
} else {
while (src != end) {
*dest = std::move(*src);
++src; ++dest;
}
}
return dest;
}
// Merge using move-semantics
template<class inPtr, class outPtr, class Prd>
outPtr merge(inPtr first1, inPtr last1,
inPtr first2, inPtr last2,
outPtr d_first,
Prd comp)
{
using value_type = typename
std::remove_reference<decltype(*first1)>::type;
if constexpr (std::is_trivially_copyable_v<value_type>) {
return std::merge(first1, last1, first2, last2, d_first, comp);
} else {
for (; first1 != last1; ++d_first) {
if (first2 == last2) {
return dj::move(first1, last1, d_first);
}
if (comp(*first2 , *first1)) {
*d_first = std::move(*first2);
++first2;
} else {
*d_first = std::move(*first1);
++first1;
}
}
return dj::move(first2, last2, d_first);
}
}
}
using namespace std;
using A = int; // Or whatever
void f4( vector<A>& v1, vector<A>& v2) {
sort(v2.begin(), v2.end());
vector<A> ret(v1.size() + v2.size());
dj::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), ret.begin(), less<>());
ret.erase( unique( ret.begin(), ret.end() ), ret.end() );
swap(v1, ret);
}