使用 goto 进行优化
Using goto for optimization
嗨,我在想,如果使用 goto 是优化的好习惯。我知道这是一件野蛮的事情,但是,说真的。
比如这样写:
switch(command[0].cmd)
{
case 0: // turn off
s::s_off();
S_time = command[0].length;
break;
case 1: // turn on
s::s_on();
S_time = command[0].length;
break;
case 4: // nop
break;
}
像这样:
switch(command[0].cmd)
{
case 0: // turn off
s::s_off();
goto a;
break;
case 1: // turn on
s::s_on();
goto a;
break;
case 4: // nop
goto b;
break;
}
a:
S_time = command[0].length;
b:
确实,如果可能的话,避免 goto
是明智的,并相信编译器会为您进行优化。即使在您的情况下,也有另一种方法可以避免代码重复:
/*possibly inline*/ void foo(/*pass necessary parameters*/)
{
switch(command[0].cmd){
case 0: // turn off
s::s_off();
break;
case 1: // turn on
s::s_on();
break;
case 4: // nop
return;
}
S_time = command[0].length;
}
不建议经常使用goto
。但它不是 "evil",在许多情况下,为了避免 "evil" 而放置 1 个 goto 而不是更复杂的逻辑要容易得多。在您的示例中,单个 goto 就足够了:
switch(command[0].cmd)
{
case 0: // turn off
s::s_off();
break;
case 1: // turn on
s::s_on();
break;
case 4: // nop
goto b; // break after goto is useless
}
S_time = command[0].length;
b:
使用goto
应该是为了让代码更清晰。没有更复杂的。这就是为什么有时可以接受从一组深层嵌套的循环或 if-conditions 中跳出,但是使用 goto
到 "optimise" 这个问题的示例中的代码不是特别好的原因 - 它不会使代码更清晰,而是相反。
通常还有其他解决方案 - 将某些东西分解成更小的函数 [无论如何通常都是好事] 或重构代码。但是,如果由于 "avoid goto at all costs",您最终得到了更复杂的代码,那么您可能走错了路。
这个问题举例说明了使用 goto 确实可能比问题本身选择的更好的选择:
What are some better ways to avoid the do-while(0); hack in C++?
就优化而言,分析是您能做的最好的事情。但是,让我们看一下生成的程序集,因为它也很有用。我将使用以下虚拟声明:
namespace s {
void s_on();
void s_off();
};
struct Command {
int cmd;
int length;
};
int S_time;
在 -O3
使用 Clang 编译的代码的第一个版本产生:
foo(Command*): # @foo(Command*)
push rbx
mov rbx, rdi
mov eax, dword ptr [rbx]
cmp eax, 1
je .LBB0_3
test eax, eax
jne .LBB0_5
call s::s_off()
jmp .LBB0_4
.LBB0_3:
call s::s_on()
.LBB0_4:
mov eax, dword ptr [rbx + 4]
mov dword ptr [rip + S_time], eax
.LBB0_5:
pop rbx
ret
而第二个版本 goto
产生:
foo2(Command*): # @foo2(Command*)
push rbx
mov rbx, rdi
mov eax, dword ptr [rbx]
cmp eax, 4
je .LBB1_6
cmp eax, 1 # These two instructions
je .LBB1_4 # weren't here in the first version
test eax, eax
jne .LBB1_5
call s::s_off()
jmp .LBB1_5
.LBB1_4:
call s::s_on()
.LBB1_5:
mov eax, dword ptr [rbx + 4]
mov dword ptr [rip + S_time], eax
.LBB1_6:
pop rbx
ret
不像其他一些情况那么清楚,但有 一个 区别:第一个版本只比较 command[0].cmd
与 0
和 1
,但第二个将它与 0
、1
和 4
进行比较。更少的代码重复并不一定意味着更优化的代码:您实际上阻碍了优化器并使其为 4
.
生成无用的特例
goto
并不是批评者所描述的充满神奇 low-level 优化光环的 low-level 工具。它只是一个非常基本的流控制工具,当其他工具不支持它时,它很少(但仍然有一些!)在 C++ 中使用。当然,它可以用于优化,但并不比其他任何一个更好或更容易。
嗨,我在想,如果使用 goto 是优化的好习惯。我知道这是一件野蛮的事情,但是,说真的。
比如这样写:
switch(command[0].cmd)
{
case 0: // turn off
s::s_off();
S_time = command[0].length;
break;
case 1: // turn on
s::s_on();
S_time = command[0].length;
break;
case 4: // nop
break;
}
像这样:
switch(command[0].cmd)
{
case 0: // turn off
s::s_off();
goto a;
break;
case 1: // turn on
s::s_on();
goto a;
break;
case 4: // nop
goto b;
break;
}
a:
S_time = command[0].length;
b:
确实,如果可能的话,避免 goto
是明智的,并相信编译器会为您进行优化。即使在您的情况下,也有另一种方法可以避免代码重复:
/*possibly inline*/ void foo(/*pass necessary parameters*/)
{
switch(command[0].cmd){
case 0: // turn off
s::s_off();
break;
case 1: // turn on
s::s_on();
break;
case 4: // nop
return;
}
S_time = command[0].length;
}
不建议经常使用goto
。但它不是 "evil",在许多情况下,为了避免 "evil" 而放置 1 个 goto 而不是更复杂的逻辑要容易得多。在您的示例中,单个 goto 就足够了:
switch(command[0].cmd)
{
case 0: // turn off
s::s_off();
break;
case 1: // turn on
s::s_on();
break;
case 4: // nop
goto b; // break after goto is useless
}
S_time = command[0].length;
b:
使用goto
应该是为了让代码更清晰。没有更复杂的。这就是为什么有时可以接受从一组深层嵌套的循环或 if-conditions 中跳出,但是使用 goto
到 "optimise" 这个问题的示例中的代码不是特别好的原因 - 它不会使代码更清晰,而是相反。
通常还有其他解决方案 - 将某些东西分解成更小的函数 [无论如何通常都是好事] 或重构代码。但是,如果由于 "avoid goto at all costs",您最终得到了更复杂的代码,那么您可能走错了路。
这个问题举例说明了使用 goto 确实可能比问题本身选择的更好的选择: What are some better ways to avoid the do-while(0); hack in C++?
就优化而言,分析是您能做的最好的事情。但是,让我们看一下生成的程序集,因为它也很有用。我将使用以下虚拟声明:
namespace s {
void s_on();
void s_off();
};
struct Command {
int cmd;
int length;
};
int S_time;
在 -O3
使用 Clang 编译的代码的第一个版本产生:
foo(Command*): # @foo(Command*)
push rbx
mov rbx, rdi
mov eax, dword ptr [rbx]
cmp eax, 1
je .LBB0_3
test eax, eax
jne .LBB0_5
call s::s_off()
jmp .LBB0_4
.LBB0_3:
call s::s_on()
.LBB0_4:
mov eax, dword ptr [rbx + 4]
mov dword ptr [rip + S_time], eax
.LBB0_5:
pop rbx
ret
而第二个版本 goto
产生:
foo2(Command*): # @foo2(Command*)
push rbx
mov rbx, rdi
mov eax, dword ptr [rbx]
cmp eax, 4
je .LBB1_6
cmp eax, 1 # These two instructions
je .LBB1_4 # weren't here in the first version
test eax, eax
jne .LBB1_5
call s::s_off()
jmp .LBB1_5
.LBB1_4:
call s::s_on()
.LBB1_5:
mov eax, dword ptr [rbx + 4]
mov dword ptr [rip + S_time], eax
.LBB1_6:
pop rbx
ret
不像其他一些情况那么清楚,但有 一个 区别:第一个版本只比较 command[0].cmd
与 0
和 1
,但第二个将它与 0
、1
和 4
进行比较。更少的代码重复并不一定意味着更优化的代码:您实际上阻碍了优化器并使其为 4
.
goto
并不是批评者所描述的充满神奇 low-level 优化光环的 low-level 工具。它只是一个非常基本的流控制工具,当其他工具不支持它时,它很少(但仍然有一些!)在 C++ 中使用。当然,它可以用于优化,但并不比其他任何一个更好或更容易。