想象一下以下情况:
struct Args
{
bool arg;
};
void thing(Args arg)
{
if(arg.arg)
cout<<"arg.arg is true\n";
else
cout<<"arg.arg is false\n";
}
int main()
{
Args a;
a.arg=false;
thing(a);
}
编译器是否足够智能删除switch
,if
和else
分支,这些分支在程序过程中显然永远不会被调用?控制这些陈述的问题必须是const
吗?最后,除了使用preproccesor之外,完全不使用变量是正确的(我对这段代码的想法很害怕)?
只是为了澄清,真实情况是我正在编写一个类,程序员可以选择是否启用某个功能。禁用该功能可以在服务器上节省大量处理时间,并在类和服务器之间留出一些带宽。我试图弄清楚是否应该使用变量作为构造函数参数,前处理程序派生或其他解决方案。如果禁用该功能,如果启用该功能,我甚至不想考虑逻辑分支。我知道使用preproccessor解决方案会做到这一点,但我想避免大量使用#ifdef
,#elseif
,我希望能够重用一个编译的共享对象。对程序员开放的源是没有问题的,因为这将是开源的。
jne
)。这是集会,如果有人可以做到:
.file "blah.cpp"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "arg.arg is true\n"
.LC1:
.string "arg.arg is false\n"
.text
.p2align 4,,15
.globl _Z5thing4Args
.type _Z5thing4Args, @function
_Z5thing4Args:
.LFB1003:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
movl %esp, %ebp
.cfi_offset 5, -8
.cfi_def_cfa_register 5
subl $24, %esp
cmpb $0, 8(%ebp)
jne .L5
movl $17, 8(%esp)
movl $.LC1, 4(%esp)
movl $_ZSt4cout, (%esp)
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
leave
.cfi_remember_state
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.p2align 4,,7
.p2align 3
.L5:
.cfi_restore_state
movl $16, 8(%esp)
movl $.LC0, 4(%esp)
movl $_ZSt4cout, (%esp)
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
leave
.cfi_def_cfa 4, 4
.cfi_restore 5
ret
.cfi_endproc
.LFE1003:
.size _Z5thing4Args, .-_Z5thing4Args
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB1004:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
movl %esp, %ebp
.cfi_offset 5, -8
.cfi_def_cfa_register 5
andl $-16, %esp
subl $16, %esp
movl $17, 8(%esp)
movl $.LC1, 4(%esp)
movl $_ZSt4cout, (%esp)
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
xorl %eax, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1004:
.size main, .-main
.p2align 4,,15
.type _GLOBAL__I__Z5thing4Args, @function
_GLOBAL__I__Z5thing4Args:
.LFB1009:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
movl %esp, %ebp
.cfi_offset 5, -8
.cfi_def_cfa_register 5
subl $24, %esp
movl $_ZStL8__ioinit, (%esp)
call _ZNSt8ios_base4InitC1Ev
movl $__dso_handle, 8(%esp)
movl $_ZStL8__ioinit, 4(%esp)
movl $_ZNSt8ios_base4InitD1Ev, (%esp)
call __cxa_atexit
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1009:
.size _GLOBAL__I__Z5thing4Args, .-_GLOBAL__I__Z5thing4Args
.section .ctors,"aw",@progbits
.align 4
.long _GLOBAL__I__Z5thing4Args
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.weakref _ZL20__gthrw_pthread_oncePiPFvvE,pthread_once
.weakref _ZL27__gthrw_pthread_getspecificj,pthread_getspecific
.weakref _ZL27__gthrw_pthread_setspecificjPKv,pthread_setspecific
.weakref _ZL22__gthrw_pthread_createPmPK14pthread_attr_tPFPvS3_ES3_,pthread_create
.weakref _ZL20__gthrw_pthread_joinmPPv,pthread_join
.weakref _ZL21__gthrw_pthread_equalmm,pthread_equal
.weakref _ZL20__gthrw_pthread_selfv,pthread_self
.weakref _ZL22__gthrw_pthread_detachm,pthread_detach
.weakref _ZL22__gthrw_pthread_cancelm,pthread_cancel
.weakref _ZL19__gthrw_sched_yieldv,sched_yield
.weakref _ZL26__gthrw_pthread_mutex_lockP15pthread_mutex_t,pthread_mutex_lock
.weakref _ZL29__gthrw_pthread_mutex_trylockP15pthread_mutex_t,pthread_mutex_trylock
.weakref _ZL31__gthrw_pthread_mutex_timedlockP15pthread_mutex_tPK8timespec,pthread_mutex_timedlock
.weakref _ZL28__gthrw_pthread_mutex_unlockP15pthread_mutex_t,pthread_mutex_unlock
.weakref _ZL26__gthrw_pthread_mutex_initP15pthread_mutex_tPK19pthread_mutexattr_t,pthread_mutex_init
.weakref _ZL29__gthrw_pthread_mutex_destroyP15pthread_mutex_t,pthread_mutex_destroy
.weakref _ZL30__gthrw_pthread_cond_broadcastP14pthread_cond_t,pthread_cond_broadcast
.weakref _ZL27__gthrw_pthread_cond_signalP14pthread_cond_t,pthread_cond_signal
.weakref _ZL25__gthrw_pthread_cond_waitP14pthread_cond_tP15pthread_mutex_t,pthread_cond_wait
.weakref _ZL30__gthrw_pthread_cond_timedwaitP14pthread_cond_tP15pthread_mutex_tPK8timespec,pthread_cond_timedwait
.weakref _ZL28__gthrw_pthread_cond_destroyP14pthread_cond_t,pthread_cond_destroy
.weakref _ZL26__gthrw_pthread_key_createPjPFvPvE,pthread_key_create
.weakref _ZL26__gthrw_pthread_key_deletej,pthread_key_delete
.weakref _ZL30__gthrw_pthread_mutexattr_initP19pthread_mutexattr_t,pthread_mutexattr_init
.weakref _ZL33__gthrw_pthread_mutexattr_settypeP19pthread_mutexattr_ti,pthread_mutexattr_settype
.weakref _ZL33__gthrw_pthread_mutexattr_destroyP19pthread_mutexattr_t,pthread_mutexattr_destroy
.ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
.section .note.GNU-stack,"",@progbits
编辑:我按照下面的建议添加asm("#aksdjfh")
之后更多地查看了程序集,我发现编译器没有摆脱它。那么#ifdef
是唯一的选择吗?或jne
指令是否可以有效地忽略性能?
答案 0 :(得分:7)
自己尝试一下:
$ g++ -O3 -S test.cpp -o test.s
-O3
开启优化,-S
告诉编译器在生成汇编代码后停止,-o
选择放置输出的位置。然后,您可以检查“test.s”文件,看看它是否进行了优化。显然这需要一些装配知识。如果您像我一样发现AT&amp; T语法不可读并且更喜欢英特尔语法,那么您可能还需要-masm=intel
。
将asm("# this is something")
之类的行添加到代码中可能会有所帮助。这些将在生成的程序集中显示为注释,这可以使您更容易识别您感兴趣的部分。
在我的机器上,GCC 4.8的快照似乎并没有优化死代码。我在每个分支中添加了一个asm注释来识别它们,并生成了这个:
.file "test.cpp"
.intel_syntax noprefix
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "arg.arg is true\n"
.LC1:
.string "arg.arg is false\n"
.text
.p2align 4,,15
.globl _Z5thing4Args
.type _Z5thing4Args, @function
_Z5thing4Args:
.LFB1215:
.cfi_startproc
sub esp, 28
.cfi_def_cfa_offset 32
cmp BYTE PTR [esp+32], 0
jne .L6
#APP
# 13 "test.cpp" 1
This is the false branch
# 0 "" 2
#NO_APP
mov DWORD PTR [esp+8], 17
mov DWORD PTR [esp+4], OFFSET FLAT:.LC1
mov DWORD PTR [esp], OFFSET FLAT:_ZSt4cout
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
add esp, 28
.cfi_remember_state
.cfi_def_cfa_offset 4
ret
.p2align 4,,7
.p2align 3
.L6:
.cfi_restore_state
#APP
# 10 "test.cpp" 1
This is the true branch
# 0 "" 2
#NO_APP
mov DWORD PTR [esp+8], 16
mov DWORD PTR [esp+4], OFFSET FLAT:.LC0
mov DWORD PTR [esp], OFFSET FLAT:_ZSt4cout
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
add esp, 28
.cfi_def_cfa_offset 4
ret
.cfi_endproc
.LFE1215:
.size _Z5thing4Args, .-_Z5thing4Args
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB1216:
.cfi_startproc
push ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
mov ebp, esp
.cfi_def_cfa_register 5
and esp, -16
sub esp, 16
mov BYTE PTR [esp], 0
call _Z5thing4Args
xor eax, eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1216:
.size main, .-main
.p2align 4,,15
.type _GLOBAL__sub_I__Z5thing4Args, @function
_GLOBAL__sub_I__Z5thing4Args:
.LFB1367:
.cfi_startproc
sub esp, 28
.cfi_def_cfa_offset 32
mov DWORD PTR [esp], OFFSET FLAT:_ZStL8__ioinit
call _ZNSt8ios_base4InitC1Ev
mov DWORD PTR [esp+8], OFFSET FLAT:__dso_handle
mov DWORD PTR [esp+4], OFFSET FLAT:_ZStL8__ioinit
mov DWORD PTR [esp], OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
call __cxa_atexit
add esp, 28
.cfi_def_cfa_offset 4
ret
.cfi_endproc
.LFE1367:
.size _GLOBAL__sub_I__Z5thing4Args, .-_GLOBAL__sub_I__Z5thing4Args
.section .init_array,"aw"
.align 4
.long _GLOBAL__sub_I__Z5thing4Args
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.hidden __dso_handle
.ident "GCC: (GNU) 4.8.0 20120311 (experimental)"
.section .note.GNU-stack,"",@progbits
如果您查找这些评论,您会发现它们都会调用某个std::cout
成员函数。
这是因为原样,该功能在其他翻译单元上可见:如果您现在使用声明nasty.cpp
制作void thing(Args arg);
文件并使用值true
进行调用,则代码必须存在。
所以我进一步尝试了一下。如果我将函数标记为static
,意味着它是该转换单元的内部函数,GCC确实优化了死代码:
.file "test.cpp"
.intel_syntax noprefix
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "arg.arg is false\n"
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB1216:
.cfi_startproc
push ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
mov ebp, esp
.cfi_def_cfa_register 5
and esp, -16
sub esp, 16
#APP
# 13 "test.cpp" 1
This is the false branch
# 0 "" 2
#NO_APP
mov DWORD PTR [esp+4], OFFSET FLAT:.LC0
mov DWORD PTR [esp], OFFSET FLAT:_ZSt4cout
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
xor eax, eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1216:
.size main, .-main
.p2align 4,,15
.type _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB1367:
.cfi_startproc
sub esp, 28
.cfi_def_cfa_offset 32
mov DWORD PTR [esp], OFFSET FLAT:_ZStL8__ioinit
call _ZNSt8ios_base4InitC1Ev
mov DWORD PTR [esp+8], OFFSET FLAT:__dso_handle
mov DWORD PTR [esp+4], OFFSET FLAT:_ZStL8__ioinit
mov DWORD PTR [esp], OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
call __cxa_atexit
add esp, 28
.cfi_def_cfa_offset 4
ret
.cfi_endproc
.LFE1367:
.size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
.section .init_array,"aw"
.align 4
.long _GLOBAL__sub_I_main
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.hidden __dso_handle
.ident "GCC: (GNU) 4.8.0 20120311 (experimental)"
.section .note.GNU-stack,"",@progbits
您不会在该代码中找到“这是真正的分支”。另外,请注意假分支如何移入main
函数并且thing
函数不再存在。 GCC只是简单地编写了函数的代码而没有生成它,因为在我添加static
之后它不会在其他任何地方使用。
如果我将其标记为inline
,它仍会在外面可见,但显然这足以让GCC对其进行优化。但是,如果这样做,则必须确保其他翻译单元看到相同的定义,以便可以根据需要为每个翻译单元生成代码。
答案 1 :(得分:1)
简短的回答是否定的(至少不是我有用的gcc版本)。
很长的答案就像R. Martinho Fernandes所说:要弄清楚这样的事情,让编译器在gcc命令行上生成汇编语言输出(-S
),然后检查汇编语言吧产生。在这种情况下,相关部分看起来像这样:
数据:
LC0:
.ascii "arg.arg is true\12\0"
LC1:
.ascii "arg.arg is false\12\0"
代码:
LCFI2:
cmpb $0, 8(%ebp)
jne L5
movl $17, 8(%esp)
movl $LC1, 4(%esp)
movl $__ZSt4cout, (%esp)
call __ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
leave
ret
L5:
movl $16, 8(%esp)
movl $LC0, 4(%esp)
movl $__ZSt4cout, (%esp)
call __ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
leave
cmpb $0, 8(%ebp)
基本上是if (whatever==false)
。根据比较结果,它将继续执行LCFI2以下的代码,该代码打印出LC1,否则打印出LC0的L5。
答案 2 :(得分:1)
我不知道gcc,但是Clang是......所以gcc可能也是如此!
#include <stdio.h>
struct Args
{
bool arg;
};
static void thing(Args arg)
{
if(arg.arg)
printf("arg.arg is true\n");
else
printf("arg.arg is false\n");
}
int main()
{
Args a;
a.arg=false;
thing(a);
}
注意:使用iostream会使输出混乱,因此我将其更改为printf
样式打印。我还添加了static
以避免发出该函数,但如果没有它,它仍然是内联的。
生成以下IR:
@str = internal constant [17 x i8] c"arg.arg is false\00"
define i32 @main() nounwind uwtable {
%puts.i = tail call i32 @puts(i8* getelementptr inbounds ([17 x i8]* @str, i64 0, i64 0)) nounwind
ret i32 0
}
你会注意到:
thing
完全内联thing
未发出(static
效果)"arg.arg is true\n"
甚至没有存储(static
效果)您要查找的优化名称是 Constant Propagation 。
答案 3 :(得分:1)
这种类型的优化实际上要求很多,当代码变得稍微复杂时,答案可能会有所不同。
只是为了澄清,真实的情况是我正在编写一个程序员可以选择是否启用某个功能的类。禁用该功能可以在服务器上节省大量处理时间,并在类和服务器之间留出一些带宽。我试图弄清楚是否应该使用变量作为构造函数参数,前处理衍生物或其他解决方案。
你可以考虑使用由Andre Alexandrescu推广的Policies。
答案 4 :(得分:0)
是的,但前提是编译器可以看到完整路径(并且启用了优化)。您可以使用GCC -S
标志进行验证。链接时优化(-flto
用于最新的GCC版本)也可以这样做,但我从未检查过。
作为源代码中#ifdef
魔术的替代方案,请考虑将功能划分为不同的实现文件,并使用make(1)
在构建时选择正确的文件。
答案 5 :(得分:0)
有许多因素可能允许编译器优化函数调用。
首先,没有证据表明'thing'函数在其他任何地方都没有被调用,因此它肯定不会自动内联,除非它被声明为静态(使其成为当前翻译单元的本地)。
您可以通过在声明'thing'函数时明确添加inline
修饰符来将这些提示提供给编译器。
内联修饰符将允许编译器扩展函数调用它的代码。
在你的情况下,内联肯定会优化if下的代码,只有第二个“cout”才能生存。
但是,我倾向于避免依赖于编译时功能启用/禁用的这种行为。
基本上,它使代码的主要逻辑与配置无法区分。这将使调试变得更加复杂。
虽然我倾向于同意许多#ifdef /#endif不是很漂亮,但对于像你这样的情况,它们非常常见。
你有另一个解决方案,我个人倾向于尽可能使用:
如果您需要启用/禁用的'功能'可以很好地隔离,您可以创建表示每种可能行为的类或函数。 将每个变体放在自己的cpp文件中,并使用构建配置系统选择在构建时编译哪个文件。
答案 6 :(得分:0)
正如其他答案直接回答了问题,这是解决问题的另一种方法:您可以使用模板。这些将始终根据您的意愿进行优化,因为它们在技术上有两种不同的功能。
template<bool do_thing_1>
void thing(std::integral_constant<bool, do_thing_1>)
{
if (do_thing_1)
cout<<"arg.arg is true\n";
else
cout<<"arg.arg is false\n";
}
int main() {
thing(std::true_type());
thing(std::false_type());
}
如果你想让它依赖一个变量,那就没有优化它,你拥有的是最好的。