我有一个代码示例,当结构化为类变量时,直接优化不起作用,但作为局部变量起作用;我想知道:为什么优化不会发生在类变量公式上?
我的示例代码的目的是让一个类在构造时启用或禁用,并且可能在其生命周期内更改。我希望,当对象的整个生命周期被禁用时,编译器会优化掉在启用对象时有条件执行的所有代码。
具体来说,我有一个std :: ofstream,我只想在“启用”时写入。禁用时,我希望跳过所有格式化输出。 (我的真正的课程是自己的,非平凡的消息格式化。)
我发现当我把它表示为一个类时,我没有得到我期望的优化。但是,如果我将代码全部复制为局部变量,我确实看到了预期的行为。
此外,我发现如果我不在示例类的方法体中的任何地方进行std :: ofstream调用,如'open','exceptions'或'clear',我也会得到预期的优化。 (但是,我的设计需要在std :: ofstream上进行这样的调用,所以对我来说这是一个没有实际意义的点。)下面的代码使用MACRO DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR来允许我们尝试这种情况。
我的示例代码使用'asm'表达式将注释插入到生成的汇编代码中。如果在程序集中检查编译器的输出,我希望在'disabled-test'注释之间没有汇编。我正在观察'class disabled-test'评论之间的汇编,但是'locals disabled-test'评论之间没有汇编。
输入C ++代码:
#include <fstream> // ofstream
#define DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR 0
class Test_Ofstream
{
public:
Test_Ofstream( const char a_filename[],
bool a_b_enabled )
#if DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR
: m_ofstream( a_filename ),
m_b_enabled( a_b_enabled )
{
}
#else
: m_ofstream(),
m_b_enabled( a_b_enabled )
{
m_ofstream.open( a_filename );
}
#endif
void write_test()
{
if( m_b_enabled )
{
m_ofstream << "Some text.\n";
}
}
private:
std::ofstream m_ofstream;
bool m_b_enabled;
};
int main( int argc, char* argv[] )
{
{
Test_Ofstream test_ofstream( "test.txt", true );
asm( "# BEGIN class enabled-test" );
test_ofstream.write_test();
asm( "# END class enabled-test" );
}
{
Test_Ofstream test_ofstream( "test.txt", false );
asm( "# BEGIN class disabled-test" );
test_ofstream.write_test();
asm( "# END class disabled-test" );
}
{
bool b_enabled = true;
#if DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR
std::ofstream test_ofstream( "test.txt" );
#else
std::ofstream test_ofstream;
test_ofstream.open( "test.txt" );
#endif
asm( "# BEGIN locals enabled-test" );
if( b_enabled )
{
test_ofstream << "Some text.\n";
}
asm( "# END locals enabled-test" );
}
{
bool b_enabled = false;
#if DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR
std::ofstream test_ofstream( "test.txt" );
#else
std::ofstream test_ofstream;
test_ofstream.open( "test.txt" );
#endif
asm( "# BEGIN locals disabled-test" );
if( b_enabled )
{
test_ofstream << "Some text.\n";
}
asm( "# END locals disabled-test" );
}
return 0;
}
输出汇编代码:
##### Cut here. #####
#APP
# 53 "test_ofstream_optimization.cpp" 1
# BEGIN class disabled-test
# 0 "" 2
#NO_APP
cmpb $0, 596(%esp)
je .L22
movl $.LC1, 4(%esp)
movl %ebx, (%esp)
.LEHB9:
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
.LEHE9:
.L22:
#APP
# 55 "test_ofstream_optimization.cpp" 1
# END class disabled-test
# 0 "" 2
#NO_APP
##### Cut here. #####
#APP
# 116 "test_ofstream_optimization.cpp" 1
# BEGIN locals disabled-test
# 0 "" 2
# 121 "test_ofstream_optimization.cpp" 1
# END locals disabled-test
# 0 "" 2
#NO_APP
##### Cut here. #####
我意识到这可能与我正在使用的编译器有关,即:g ++ - 4.6(Debian 4.6.1-4)4.6.1;编译器标志:-Wall -S -O2。然而,这似乎是一个简单的优化,我发现很难相信这可能是编译器弄乱了。
非常感谢任何帮助,见解或指导。
答案 0 :(得分:5)
非常简单。当您将代码直接编写为局部变量时,代码将内联,编译器将执行常量折叠。当您在类范围内时,代码未内联且m_b_enabled
的值未知,因此编译器必须执行调用。为了证明代码在语义上相等并执行此优化,不仅必须内联该调用,而且还必须每次访问该类。编译器可能会认为内联类不会产生足够的好处。编译器也可以选择不内联代码,因为他们不知道如何,并且内联asm
表达式正是可能导致他们这样做的事情,因为编译器不知道如何处理汇编代码
通常,您会放置一个断点并检查反汇编。无论如何,这就是我在Visual Studio中所做的事情。任何类型的内联汇编程序都可能对优化程序造成破坏。
当我删除汇编程序表达式时,Visual Studio会内联代码 - 并且无论如何都不会立即执行优化。堆叠优化过程的问题在于,您永远无法获得正确的顺序来查找所有可能的优化。
答案 1 :(得分:0)
正如你所说,这将取决于编译器。但我的猜测是:
优化器可以证明没有其他代码可以修改对象bool b_enabled
,因为它是本地的,你永远不会获取它的地址或绑定对它的引用。本地版本很容易优化。
当DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR
为真时,Test_Ofstream
构造函数:
ofstream(const char*)
m_b_enabled
由于在初始化test_ofstream.m_b_enabled
和测试它之间没有任何操作,这种优化只是有点棘手,但听起来像g ++仍在管理它。
当DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR
为false时,Test_Ofstream
构造函数:
ofstream
默认构造函数m_b_enabled
m_ofstream.open(const char*)
不允许优化器假定ofstream::open
不会更改test_ofstream.m_b_enabled
。我们知道它不应该,但理论上非内联库函数可以找出包含其'this'参数的完整对象test_ofstream
,并以这种方式修改它。