我最近阅读了std::move
如何通过移动值而不是复制它来加速代码。所以我制作了一个测试程序,用std::vector
来比较速度。
代码:
#include <iostream>
#include <vector>
#include <stdint.h>
#ifdef WIN32
#include <Windows.h>
#else
#include <sys/time.h>
#include <ctime>
#endif
#undef max
// Returns the amount of milliseconds elapsed since the UNIX epoch. Works on both
// windows and linux.
uint64_t GetTimeMs64()
{
#ifdef _WIN32
// Windows
FILETIME ft;
LARGE_INTEGER li;
// Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it
// to a LARGE_INTEGER structure.
GetSystemTimeAsFileTime(&ft);
li.LowPart = ft.dwLowDateTime;
li.HighPart = ft.dwHighDateTime;
uint64_t ret = li.QuadPart;
ret -= 116444736000000000LL; // Convert from file time to UNIX epoch time.
ret /= 10000; // From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals
return ret;
#else
// Linux
struct timeval tv;
gettimeofday(&tv, NULL);
uint64 ret = tv.tv_usec;
// Convert from micro seconds (10^-6) to milliseconds (10^-3)
ret /= 1000;
// Adds the seconds (10^0) after converting them to milliseconds (10^-3)
ret += (tv.tv_sec * 1000);
return ret;
#endif
}
static std::vector<std::string> GetVec1()
{
std::vector<std::string> o(100000, "abcd");
bool tr = true;
if (tr)
return std::move(o);
return std::move(std::vector<std::string>(100000, "abcd"));
}
static std::vector<std::string> GetVec2()
{
std::vector<std::string> o(100000, "abcd");
bool tr = true;
if (tr)
return o;
return std::vector<std::string>(100000, "abcd");
}
int main()
{
uint64_t timer;
std::vector<std::string> vec;
timer = GetTimeMs64();
for (int i = 0; i < 1000; ++i)
vec = GetVec1();
std::cout << GetTimeMs64() - timer << " timer 1(std::move)" << std::endl;
timer = GetTimeMs64();
for (int i = 0; i < 1000; ++i)
vec = GetVec2();
std::cout << GetTimeMs64() - timer << " timer 2(no move)" << std::endl;
std::cin.get();
return 0;
}
我得到了以下结果:
发布(x86)/ O2。 tr = true
4376计时器1(std :: move)
4191计时器2(不动)
发布(x86)/ O2。 tr = false
7311计时器1(std :: move)
7301计时器2(不动)
两个计时器之间的结果非常接近,并没有太大差别。我已经假设这是因为返回值优化(RVO)这意味着我在没有我知道的情况下已经按编译器移动了我的返回值,对吧?
然后我在没有任何优化的情况下运行新测试以确保我是对的。 结果:
发布(x86)/ Od。 tr = true
40860计时器1(std :: move)
40863计时器2(不动)
发布(x86)/ Od。 tr = false
83567计时器1(std :: move)
82075计时器2(不动)
现在即使/ O2和/ Od之间的差异真的很大,但是不移动或std::move
之间的区别(甚至tr
之间的区别为true
或false
)是最小的。
这是否意味着即使禁用了优化,也允许编译器应用RVO
或std::move
的速度不如我想象的那么快?
答案 0 :(得分:9)
您缺少一个基本信息:标准明确强制执行当return
语句(以及一些其他不太常见的上下文)指定函数局部变量(例如{{1}在你的情况下),首先执行从参数构造返回值的重载决策,就好像参数是一个rvalue(即使它不是)。只有在失败的情况下才能使用左值再次完成重载决策。这由C ++ 14 12.8 / 32涵盖; C ++ 11中存在类似的措辞。
12.8 / 32 当满足复制/移动操作的省略标准时,但异常声明,和 要复制的对象由左值,指定,或者在
o
语句中的表达式为(可能) 括号中的 id-expression ,用于在主体中声明自动存储持续时间的对象 最里面的封闭函数的 parameter-declaration-clause 或 lambda-expression,重载解析 首先执行选择复制的构造函数,就像对象是由右值指定的一样。如果 第一个重载决策失败或未执行,或者如果所选的第一个参数的类型 构造函数不是对象类型的rvalue引用(可能是cv-qualified),重载决策是 再次执行,将对象视为左值。 [注意:这个两阶段的重载决议必须是 无论是否会发生复制,都会执行。如果是elision,它确定要调用的构造函数 未执行,即使呼叫被省略,也必须可以访问所选的构造函数。 -end note ] ...
(强调我的)
因此,在返回函数范围自动变量时,每个return
语句中都存在不可避免的隐式std::move
。
在return语句中使用return
是一个悲观化。它会阻止NRVO,并且由于“隐式尝试rvalue first”规则而无法获得任何结果。
答案 1 :(得分:4)
即使您指定了/Od
,编译器也会执行RVO。允许通过C ++标准(Kerrek SB指出的§12.8/ 31,32)这样做。
如果您真的想看到差异,可以将变量声明为volatile
。这将禁止编译器对其执行RVO。 (§12.8/ 31项目1)