问题 自动xvalue优化


有点令人惊讶(对我来说),以下两个程序编译到不同的输出,后者有更好的性能(用gcc和clang测试):

#include <vector>
int main()
{
    std::vector<int> a(2<<20);
    for(std::size_t i = 0; i != 1000; ++i) {
        std::vector<int> b(2<<20);
        a = b;
    }
}

#include <vector>
int main()
{
    std::vector<int> a(2<<20);
    for(std::size_t i = 0; i != 1000; ++i) {
        std::vector<int> b(2<<20);
        a = std::move(b);
    }
}

有人可以向我解释为什么编译器会(或可以)不自动考虑 b 最后一个赋值中的xvalue并应用不带显式的移动语义 std::move 投?

编辑:编译 (g++|clang++) -std=c++11 -O3 -o test test.cpp


10310
2017-09-24 09:22


起源

你传递给编译器的参数是什么? - Joe
我的第一个猜测是,这将以一种意想不到的方式改变程序的语义,将副本转换为移动。 - pmr
@pmr:这也是我怀疑的,但我真的很想明白为什么。天真地,它看起来确实像xvalue应该对我来说。 - Xoph
确实,它可能是一种优化,但它肯定会影响程序本身的语义,即改变所有权。 IIRC在第一次标准草案期间就这一主题进行了大量讨论 - Marco A.
编译器只是安全,在这种情况下它确实可以工作,但似乎编译器在进行优化时没有使用“b将不会再次使用”的信息,而std :: move明确告诉他 - meneldal


答案:


编译器不能破坏as-if规则

如§1.9/ 1所述:

本国际标准中的语义描述定义了一个   参数化的非确定性抽象机器。这个国际   标准对合格结构没有要求   实现。特别是,他们不需要复制或模仿   抽象机器的结构。相反,符合实施   需要模仿(仅)抽象的可观察行为   机器如下所述

即编译器无法更改程序的可观察行为。自动(即使没有任何影响)将分配转换为移动分配会破坏此陈述。

复制符号可以稍微改变这种行为,但这受§12.8/ 31的约束。

如果您想使用移动版本,则必须在后一个示例中明确要求它。


6
2017-09-24 09:42



好的,特别是程序员应该有可靠的复制/移动操作员/ ctor调用。我不知何故认为要求程序员在语义上使这些操作兼容是明智的。我想这两种方法都有其优点和缺点,但我可以看到为什么标准会看到不同的方式。 - Xoph
在这个特定的代码中,它不会破坏as-if规则,因为没有输出 - M.M


让我们看看下一个样本(请忽略 void 返回类型 operator=):

#include <iostream>

struct helper
{
    void operator=(helper&&){std::cout<<"move"<<std::endl;}
    void operator=(const helper&){std::cout<<"copy"<<std::endl;}
};

void fun()
{
    helper a;
    {
        helper b;
        a = b;
    }
}

void gun()
{
    helper a;
    {
        helper b;
        a = std::move(b);
    }
}
int main()
{
    fun();
    gun();
}

operator= 根据其参数有不同的行为。只有在能够保持可观察行为相同的情况下,才允许编译器优化代码。

考虑到 b 从 fun 一个 xvalue,虽然它不是 xvalue 在调用的那一刻,它将改变程序的可观察行为,这不是标准所希望的。


5
2017-09-24 09:38



谢谢,我知道构造函数调用的变化。我假设程序员以某种方式需要使移动构造函数/运算符和复制构造函数/运算符“语义匹配”,我猜这是我的缺陷! - Xoph