【问题标题】:std::move and RVO optimizationsstd::move 和 RVO 优化
【发布时间】:2015-09-29 13:12:30
【问题描述】:

我最近阅读了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 之间的差异非常显着,但 no move 或 std::move(甚至 trtruefalse)之间的差异很小。

这是否意味着即使禁用了优化,编译器也可以应用RVO 还是std::move 没有我想象的那么快?

【问题讨论】:

  • 你看过生成的指令了吗?
  • 附注:您应该查看std::chrono::high_resolution_clock 以满足您的时间需求...
  • 这是 12.8,第 31、32 段。
  • 为了真正看到差异,请使用重对象进行复制。使用某些自定义类型的对象,该对象具有非常“昂贵”的复制构造,例如复制图像缓冲区。

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


【解决方案1】:

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

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

(强调我的)

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

在返回语句中使用std::move,如果有的话,是一种悲观由于“隐式首先尝试右值”规则,它会阻止 NRVO,并且不会得到任何东西。 p>

【讨论】:

  • @MatthiasVegh 添加了标准的相关部分。
【解决方案2】:

即使您指定了/Od,编译器也会执行 RVO。 C++ 标准允许这样做(Kerrek SB 指出的§12.8/31,32)

如果你真的想看看区别,你可以将你的变量声明为volatile。这将禁止编译器对其执行 RVO。 (§12.8/31 第 1 项)

【讨论】:

    猜你喜欢
    • 2013-10-16
    • 2022-10-01
    • 1970-01-01
    • 2016-07-21
    • 1970-01-01
    • 2019-01-13
    • 2013-12-19
    相关资源
    最近更新 更多