std :: move和RVO优化

时间:2015-09-29 13:12:30

标签: c++ c++11 optimization visual-studio-2015 return-value-optimization

我最近阅读了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之间的区别为truefalse )是最小的。

这是否意味着即使禁用了优化,也允许编译器应用RVOstd::move的速度不如我想象的那么快?

2 个答案:

答案 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)