我想循环一个序列,但我想动态选择在序列中启动循环的位置。我设计了这种流动模式。
switch(offset){
start:
currentObject = objects[index++]; //a different object is chosen to be manipulated by the sequence of code
case 0:
sub_sequence(currentObject); // a sequence that is repeated within the larger sequence of the entire switch
if(enough_actions) break;
case 1:
sub_sequence(currentObject);
if(enough_actions) break;
case 2:
sub_sequence(currentObject);
if(enough_actions) break;
goto start;
}
它似乎很符合我的需求,但我以前从未见过这种设计。这个设计有什么问题吗?我应该倾向于使用替代方案吗?
答案 0 :(得分:4)
你构建的是 Duff的设备。虽然它避免了重复的源代码,但它不仅难以理解人类,而且同样难以为编译器进行优化。
switch(offset)
{
case 0:
sub_sequence(currentObject); // a sequence that is repeated within the larger sequence of the entire switch
if(enough_actions) break;
case 1:
sub_sequence(currentObject);
if(enough_actions) break;
case 2:
sub_sequence(currentObject);
if(enough_actions) break;
//a different object is chosen to be manipulated by the sequence of code
currentObject = objects[index++];
while(true) {
sub_sequence(currentObject);
if(enough_actions) break;
sub_sequence(currentObject);
if(enough_actions) break;
sub_sequence(currentObject);
if(enough_actions) break;
currentObject = objects[index++];
}
}
通过将循环与变量入口点分开,可以让编译器更自由地执行优化。
在原始代码中,它由start:
标签和3个case:
标签分隔,强制编译器分别处理两个标签之间的每个代码部分。
如果没有这些标签,编译器现在可以将特定于switch语句的优化应用于switch
块,以及可能的其他循环展开或while
循环的其他策略。
最后,寻找更易读的变体可能会产生更紧凑的和更快的机器代码。
这可以说是少数几个可以接受“复制”代码的情况之一,因为switch
和while
块只看起来相似,但仍然表现完全不同。
EDIT1:将循环移至switch语句的末尾,以便正确处理enough_actions
。如果没有提前退出的条件,则循环可以放在switch
块之外。
奖励:免费切换实施:
for(;!enough_actions;offset = 0,currentObject = objects[index++]) {
for(int i = offset; i < 3 && !enough_actions; i++) {
sub_sequence(currentObject);
}
}
答案 1 :(得分:3)
你也可以这样做:
switch(offset)
{
do
{
currentObject = objects[index++]; //a different object is chosen to be manipulated by the sequence of code
case 0:
sub_sequence(); // a sequence that is repeated within the larger sequence of the entire switch
if(enough_actions) break;
case 1:
sub_sequence();
if(enough_actions) break;
case 2:
sub_sequence();
if(enough_actions) break;
}
while (1);
}
所以你要避免转到;)
(如评论中所述,从技术上讲,如果需要这种行为,则无法避免goto
)
但是,你是对的,两者都应该符合你的需要。
答案 2 :(得分:1)
我已经检查了Microsoft编译器为下面的fibonacci函数生成的汇编代码,并且编译器仍然可以稍微修改展开的循环序列(我假设优化寄存器依赖性)。
unsigned int fib(unsigned int n)
{
unsigned int f0, f1;
f0 = n & 1; /* if n even, f0=0=fib(0), f1=1=fib(-1) */
f1 = 1 - f0; /* else f1=0=fib(0), f0=1=fib(-1) */
switch(n%8){
do{
f1 += f0;
case 7:
f0 += f1;
case 6:
f1 += f0;
case 5:
f0 += f1;
case 4:
f1 += f0;
case 3:
f0 += f1;
case 2:
f1 += f0;
case 1:
f0 += f1;
case 0:
continue;
}while(0 <= (int)(n -= 8));
}
return f0;
}
制作汇编代码:
_fib PROC ; _n$ = eax
push esi
mov esi, eax
and eax, 1
mov edx, esi
mov ecx, 1
and edx, 7
sub ecx, eax
cmp edx, 7
ja SHORT $LN9@fib
jmp DWORD PTR $LN17@fib[edx*4]
$LN10@fib:
sub esi, 8
js SHORT $LN9@fib
add ecx, eax
$LN8@fib:
add eax, ecx
$LN7@fib:
add ecx, eax
$LN6@fib:
add eax, ecx
$LN5@fib:
add ecx, eax
$LN4@fib:
add eax, ecx
$LN3@fib:
add ecx, eax
$LN2@fib:
add eax, ecx
jmp SHORT $LN10@fib
$LN9@fib:
pop esi
ret 0
npad 1
$LN17@fib: ;jump table
DD $LN10@fib
DD $LN2@fib
DD $LN3@fib
DD $LN4@fib
DD $LN5@fib
DD $LN6@fib
DD $LN7@fib
DD $LN8@fib
_fib ENDP
这可能更适用于线性反馈移位寄存器,其中展开循环以节省变量之间的数据移位。例如:
while(...){
e = f(a,b,c,d);
a = b;
b = c;
c = d;
d = e;
}
展开到
do{
a = f(a,b,c,d);
case 3:
b = f(b,c,d,a);
case 2:
c = f(c,d,a,b);
case 1:
d = f(d,a,b,c);
case 0:
}while(...);
如果元素的数量不是4的倍数,那么Duff的设备用于进入展开的循环。