移动语义较慢然后复制 c++
move semantics slower then copy c++
我写了一个比较移动和测试语义的小测试:
#include <vector>
#include <iostream>
#include <iterator>
#include <chrono>
#include <iomanip>
using namespace std;
int main()
{
int lenLeft = 3;
int lenMid = 3;
vector<int> lenVec{10,100,1000,static_cast<int>(1e4),static_cast<int>(1e5),static_cast<int>(1e6),static_cast<int>(1e7)};
int reps = 100;
vector<double>delta_t_move;
vector<double>delta_t_copy;
//move
cout<<"move"<<endl;
{
for(int len : lenVec)
{
auto startTime = std::chrono::high_resolution_clock::now();
for(int i = 0; i<reps;i++)
{
vector<int> leftVec(len,0);
vector<int> rightVec;
move(leftVec.begin()+lenLeft+lenMid,leftVec.end(),std::back_inserter(rightVec));
leftVec.erase(leftVec.begin()+lenLeft+lenMid,leftVec.end());
vector<int> midVec;
std::move(leftVec.begin()+lenLeft,leftVec.begin()+lenLeft+lenMid,std::back_inserter(midVec));
leftVec.erase(leftVec.begin()+lenLeft,leftVec.begin()+lenLeft+lenMid);
}
auto endTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = endTime - startTime;
delta_t_move.push_back(elapsed.count());
}
}
//copy
cout<<"copy"<<endl;
{
for(int len : lenVec)
{
auto startTime = std::chrono::high_resolution_clock::now();
for(int i = 0; i<reps;i++)
{
vector<int> leftVec(len,0);
vector<int> rightVec = vector<int>(leftVec.begin()+lenLeft+lenMid,leftVec.end());
leftVec.erase(leftVec.begin()+lenLeft+lenMid,leftVec.end());
vector<int> midVec = vector<int>(leftVec.begin()+lenLeft,leftVec.begin()+lenLeft+lenMid);
leftVec.erase(leftVec.begin()+lenLeft,leftVec.begin()+lenLeft+lenMid);
}
auto endTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = endTime - startTime;
delta_t_copy.push_back(elapsed.count());
}
}
for(int i = 0; i<lenVec.size();i++)
{
cout<<"lenVec = "<<setw(40)<<lenVec.at(i)<<"\t\t : delta_t_copy/delta_t_move = "<< delta_t_copy.at(i)/delta_t_move.at(i)<<endl;
}
return 0;
}
我得到的这个程序的输出是:
move
copy
lenVec = 10 : delta_t_copy/delta_t_move = 0.431172
lenVec = 100 : delta_t_copy/delta_t_move = 0.257102
lenVec = 1000 : delta_t_copy/delta_t_move = 0.166006
lenVec = 10000 : delta_t_copy/delta_t_move = 0.108573
lenVec = 100000 : delta_t_copy/delta_t_move = 0.113769
lenVec = 1000000 : delta_t_copy/delta_t_move = 0.134912
lenVec = 10000000 : delta_t_copy/delta_t_move = 0.133874
我将大小为 len
的初始向量拆分为 3 部分。第一条长度3,中间一条长度3,其余尺寸len-6
。
我的结果表明复制语义比移动语义快得多。
我用的是MSVC2015。
知道这怎么可能是真的吗?什么情况下移动语义更快?
您的基准测试有缺陷且不精确。
首先,您从未提及您使用的是什么编译器标志 - 您是否启用了优化?
最大的问题是您使用的是 int
元素的向量。 int
移动在性能方面等同于 int
复制。
为了获得正确和完整的基准测试,您还应该尝试 g++ 和 clang++。
您没有在任何地方呼叫 std::vector<T>::reserve
。
虽然不太可能,但不能保证编译器不会积极优化您的循环(例如展开或融合多个循环).
您还在 "copy" 基准测试中执行了一些不必要的操作:
vector<int> rightVec = vector<int>(leftVec.begin()+lenLeft+lenMid,leftVec.end());
vector<int> midVec = vector<int>(leftVec.begin()+lenLeft,leftVec.begin()+lenLeft+lenMid);
为什么不...
vector<int> rightVec(leftVec.begin()+lenLeft+lenMid,leftVec.end());
vector<int> midVec(leftVec.begin()+lenLeft,leftVec.begin()+lenLeft+lenMid);
...?
除了 ,让我指出具体导致 "move"
代码路径性能下降的原因。
您的基准归结为这种填充向量的方式之间的区别:
vector<int> rightVec = vector<int>(startIt, endIt);
与这种填充方式对比:
vector<int> rightVec;
move(startIt, endIt, std::back_inserter(rightVec));
因为这只是 部分,您的两个代码路径差异很大。
第二个版本预计会慢一些,原因有二:
- 在第二种情况下,目标向量不知道它应该存储多少元素,因此它必须在您执行插入时不断增长。增长向量很昂贵,因为它涉及重新分配和 copying/moving 先前插入的元素。您可以通过在
move
之前插入适当的 reseve()
调用来消除此缺点。这将大大减少此代码路径上的性能损失。
- 剩下的小的性能差异将是由于您移动到
back_inserter
导致按元素插入到目标向量,正如在第一个中执行的批量插入所假设的那样案例.
如果您注意减轻这两点的影响,您会观察到运行时间大致相同,因为正如已经指出的那样,移动和复制是 int
元素的等效操作。
我写了一个比较移动和测试语义的小测试:
#include <vector>
#include <iostream>
#include <iterator>
#include <chrono>
#include <iomanip>
using namespace std;
int main()
{
int lenLeft = 3;
int lenMid = 3;
vector<int> lenVec{10,100,1000,static_cast<int>(1e4),static_cast<int>(1e5),static_cast<int>(1e6),static_cast<int>(1e7)};
int reps = 100;
vector<double>delta_t_move;
vector<double>delta_t_copy;
//move
cout<<"move"<<endl;
{
for(int len : lenVec)
{
auto startTime = std::chrono::high_resolution_clock::now();
for(int i = 0; i<reps;i++)
{
vector<int> leftVec(len,0);
vector<int> rightVec;
move(leftVec.begin()+lenLeft+lenMid,leftVec.end(),std::back_inserter(rightVec));
leftVec.erase(leftVec.begin()+lenLeft+lenMid,leftVec.end());
vector<int> midVec;
std::move(leftVec.begin()+lenLeft,leftVec.begin()+lenLeft+lenMid,std::back_inserter(midVec));
leftVec.erase(leftVec.begin()+lenLeft,leftVec.begin()+lenLeft+lenMid);
}
auto endTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = endTime - startTime;
delta_t_move.push_back(elapsed.count());
}
}
//copy
cout<<"copy"<<endl;
{
for(int len : lenVec)
{
auto startTime = std::chrono::high_resolution_clock::now();
for(int i = 0; i<reps;i++)
{
vector<int> leftVec(len,0);
vector<int> rightVec = vector<int>(leftVec.begin()+lenLeft+lenMid,leftVec.end());
leftVec.erase(leftVec.begin()+lenLeft+lenMid,leftVec.end());
vector<int> midVec = vector<int>(leftVec.begin()+lenLeft,leftVec.begin()+lenLeft+lenMid);
leftVec.erase(leftVec.begin()+lenLeft,leftVec.begin()+lenLeft+lenMid);
}
auto endTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = endTime - startTime;
delta_t_copy.push_back(elapsed.count());
}
}
for(int i = 0; i<lenVec.size();i++)
{
cout<<"lenVec = "<<setw(40)<<lenVec.at(i)<<"\t\t : delta_t_copy/delta_t_move = "<< delta_t_copy.at(i)/delta_t_move.at(i)<<endl;
}
return 0;
}
我得到的这个程序的输出是:
move
copy
lenVec = 10 : delta_t_copy/delta_t_move = 0.431172
lenVec = 100 : delta_t_copy/delta_t_move = 0.257102
lenVec = 1000 : delta_t_copy/delta_t_move = 0.166006
lenVec = 10000 : delta_t_copy/delta_t_move = 0.108573
lenVec = 100000 : delta_t_copy/delta_t_move = 0.113769
lenVec = 1000000 : delta_t_copy/delta_t_move = 0.134912
lenVec = 10000000 : delta_t_copy/delta_t_move = 0.133874
我将大小为 len
的初始向量拆分为 3 部分。第一条长度3,中间一条长度3,其余尺寸len-6
。
我的结果表明复制语义比移动语义快得多。
我用的是MSVC2015。
知道这怎么可能是真的吗?什么情况下移动语义更快?
您的基准测试有缺陷且不精确。
首先,您从未提及您使用的是什么编译器标志 - 您是否启用了优化?
最大的问题是您使用的是
int
元素的向量。int
移动在性能方面等同于int
复制。为了获得正确和完整的基准测试,您还应该尝试 g++ 和 clang++。
您没有在任何地方呼叫
std::vector<T>::reserve
。虽然不太可能,但不能保证编译器不会积极优化您的循环(例如展开或融合多个循环).
您还在 "copy" 基准测试中执行了一些不必要的操作:
vector<int> rightVec = vector<int>(leftVec.begin()+lenLeft+lenMid,leftVec.end());
vector<int> midVec = vector<int>(leftVec.begin()+lenLeft,leftVec.begin()+lenLeft+lenMid);
为什么不...
vector<int> rightVec(leftVec.begin()+lenLeft+lenMid,leftVec.end());
vector<int> midVec(leftVec.begin()+lenLeft,leftVec.begin()+lenLeft+lenMid);
...?
除了 "move"
代码路径性能下降的原因。
您的基准归结为这种填充向量的方式之间的区别:
vector<int> rightVec = vector<int>(startIt, endIt);
与这种填充方式对比:
vector<int> rightVec;
move(startIt, endIt, std::back_inserter(rightVec));
因为这只是 部分,您的两个代码路径差异很大。
第二个版本预计会慢一些,原因有二:
- 在第二种情况下,目标向量不知道它应该存储多少元素,因此它必须在您执行插入时不断增长。增长向量很昂贵,因为它涉及重新分配和 copying/moving 先前插入的元素。您可以通过在
move
之前插入适当的reseve()
调用来消除此缺点。这将大大减少此代码路径上的性能损失。 - 剩下的小的性能差异将是由于您移动到
back_inserter
导致按元素插入到目标向量,正如在第一个中执行的批量插入所假设的那样案例.
如果您注意减轻这两点的影响,您会观察到运行时间大致相同,因为正如已经指出的那样,移动和复制是 int
元素的等效操作。