std :: move和RVO优化

Som*_*ken 4 c++ optimization return-value-optimization c++11 visual-studio-2015

我最近读过如何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;
}
Run Code Online (Sandbox Code Playgroud)

我得到了以下结果:

发布(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(甚至在trtrue或之间false)之间的差异是最小的.

这是否意味着即使禁用了优化,编译器也可以应用RVOstd::move不像我想象的那样快?

Rei*_*ica 9

您缺少一个基本的信息:标准明确强制执行当return语句(以及一些其他不太常见的上下文)指定函数局部变量(例如o在您的情况下)时,重载决策以构造返回值首先执行参数,就好像参数是一个rvalue(即使它不是).只有在失败的情况下才能使用左值再次完成重载决策.这由C++ 14 12.8/32涵盖; C++ 11中存在类似的措辞.

12.8/32当满足复制/移动操作的省略标准时,但不满足异常声明,并且要复制的对象由左值指定,或者当return语句中的表达式为(可能带有括号)时id-expression,用于在最内层封闭函数或lambda-expression的body或parameter-declaration-clause声明的具有自动存储持续时间的对象的命名 ,首先执行重载决策以选择复制的构造函数,就好像该对象是由一个右值.如果第一个重载决策失败或未执行,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值.[ 注意:无论是否发生复制省略,都必须执行此两阶段重载决策.如果未执行elision,它将确定要调用的构造函数,并且即使调用被省略,也必须可以访问所选的构造函数.- 注意 ] ......

(强调我的)

因此,实际上,在返回函数范围自动变量时,每个语句中都存在一个不可避免的隐式std::move存在return.

使用std::movereturn语句中,如果有的话,一个pessimisation.它会阻止NRVO,并且由于"隐式尝试右值优先"规则而无法获得任何结果.