用作" const&"的临时对象的编译器优化循环中的函数参数?

时间:2015-08-04 22:24:22

标签: c++ c++11 visual-c++ optimization

我在调用std::this_thread::sleep_for下面有一个永久的线程循环,延迟10毫秒。持续时间是临时对象std::chrono::milliseconds(10)。延迟通话似乎"正常"和#34;典型的"下面是一些示例代码。然而,仔细观察,很明显,在每个循环中,临时持续时间对象被创建并销毁一次。

// Loop A.
for (;;)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    // Do something.
}

现在,如果在循环外创建持续时间对象(作为常量对象),则它将仅为所有循环构造一次。请参阅下面的代码。

// Loop B.
const auto t = std::chrono::milliseconds(10);
for (;;)
{
    std::this_thread::sleep_for(t);
    // Do something.
}

问题:由于std :: this_thread :: sleep_for使用" const&"作为参数类型,任何C ++编译器都会将Loop A中的临时持续时间对象优化为Loop B吗?

我在下面尝试了一个简单的测试程序。结果表明VC ++ 2013没有优化" const&"临时对象。

#include <iostream>
#include <thread>

using namespace std;

class A {
public:
    A() { cout << "Ctor.\n"; }
    void ReadOnly() const {}  // Read-only method.
};

static void Foo(const A & a)
{
    a.ReadOnly();
}

int main()
{
    cout << "Temp object:\n";
    for (int i = 0; i < 3; ++i)
    {
        Foo(A());
    }

    cout << "Optimized:\n";
    const auto ca = A();
    for (int i = 0; i < 3; ++i)
    {
        Foo(ca);
    }
}
/* VC2013 Output:
Temp object:
Ctor.
Ctor.
Ctor.
Optimized:
Ctor.
*/

2 个答案:

答案 0 :(得分:12)

MSVC和其他现代编译器完全能够优化循环中的临时对象。

您的示例中的问题是您在构造函数中有副作用。根据C ++标准,编译器不允许优化临时对象的创建/销毁,因为它不再产生相同的可观察效果(即打印3次)。

如果您不再fun(Type1.class); 某事,那么图片就完全不同了。当然,您必须查看为验证优化而生成的汇编代码。

示例:

cout

优化器完全注意到它是相同的静态变量,它会增加,而不会在其他地方使用。所以这里生成的汇编程序代码(提取):

class A {
public:
    static int k;    
    A() { k++;  }   
    void ReadOnly() const {}  // Read-only method.
};
int A::k = 0; 

// Foo unchanged

int main()
{
    for(int i = 0; i < 3; ++i)
        Foo(A());  // k++ is a side effect, but not yet observable 
    volatile int x = A::k;     // volatile can't be optimized away

    const auto ca = A();
    for(int i = 0; i < 3; ++i)
        Foo(ca);
    x = A::k;     // volatile can't be optimized away
    cout << x << endl; 
}

当然,您需要在发布模式下进行编译。

如您所见,编译器非常聪明。因此,让他完成自己的工作,专注于您的代码设计,并牢记:过早优化是所有邪恶的根源 ;-)

答案 1 :(得分:2)

假设编译器“理解”构造函数的作用(换句话说,在翻译单元中有构造函数的源代码 - 即源文件或其中一个头文件,包含了那个构造函数),那么编译器应该删除对没有副作用的构造函数的多余调用。

由于打印某些东西是A构造函数的非常明确的副作用,编译器显然无法对其进行优化。所以,编译器在这里完全做了“正确”的事情。如果你有一个锁定构造函数然后在析构函数中释放锁,那么它将非常糟糕,并且编译器决定优化你的:

for(...)
{
   LockWrapper lock_it(theLock);
   ... some code here 
}

到循环外部,因为虽然获取和释放锁的开销较低,但代码的语义会发生变化,并且锁的持续时间可能会更长,这会对使用相同的其他代码产生影响锁定,例如在不同的线程中。