对supercat的回答进行扩展。
输入以下内容:
unsigned short fun ( unsigned short x )
{
return(x/7);
}
转换为一个更大的乘积:
00000000 <fun>:
0: e59f1010 ldr r1, [pc, #16]
4: e0832190 umull r2, r3, r0, r1
8: e0400003 sub r0, r0, r3
c: e08300a0 add r0, r3, r0, lsr #1
10: e1a00120 lsr r0, r0, #2
14: e12fff1e bx lr
18: 24924925 .word 0x24924925
二进制下的1/7(长除法):
0.001001001001001
111)1.000000
111
====
1000
111
===
1
0.001001001001001001001001001001
0.0010 0100 1001 0010 0100 1001 001001
0x2492492492...
0x24924925>>32 (rounded up)
为了使这个工作正常,您需要一个64位的结果,您需要取出前一半并进行一些调整,例如:
7 * 0x24924925 = 0x100000003
你需要取前32位(虽然不完全是这么简单,但对于这个值,你可以看到它的工作原理)。
所有拇指变体乘法都是32位= 32位* 32位,因此结果将为0x00000003,这是不起作用的。
因此,我们可以使用0x24924,我们可以像supercat一样将其变成0x2493或0x2492。
现在我们可以使用32x32 = 32位乘法:
0x2492 * 7 = 0x0FFFE
0x2493 * 7 = 0x10005
让我们从较大的一个开始运行:
0x100000000/0x2493 = a number greater than 65536. so that is fine.
但是:
0x3335 * 0x2493 = 0x0750DB6F
0x3336 * 0x2493 = 0x07510002
0x3335 / 7 = 0x750
0x3336 / 7 = 0x750
所以你只能用那种方法走到这一步。
如果我们遵循手臂代码的模式:
for(ra=0;ra<0x10000;ra++)
{
rb=0x2493*ra;
rd=rb>>16;
rb=ra-rd;
rb=rd+(rb>>1);
rb>>=2;
rc=ra/7;
printf("0x%X 0x%X 0x%X \n",ra,rb,rc);
if(rb!=rc) break;
}
它可以从0x0000到0xFFFF工作,因此您可以编写汇编代码来执行此操作(请注意,需要的是0x2493而不是0x2492)。
如果您知道操作数不会超过某个值,则可以使用更多的1/7位来进行乘法运算。
无论如何,当编译器没有为您执行此优化时,您仍然有机会自己实现。
现在我想起来以前遇到过这种情况,现在有点明白了。但是我当时使用的是全尺寸的ARM,并调用了一个我在ARM模式下编译的例程(其他代码是Thumb模式),并且基本上有一个switch语句,如果分母= 1,则结果= x / 1; 如果分母= 2,则结果= x / 2等等。然后它避免了gcclib函数并生成了1 / x乘法。(我有3或4个不同的常量要除以):
unsigned short udiv7 ( unsigned short x )
{
unsigned int r0;
unsigned int r3;
r0=x;
r3=0x2493*r0;
r3>>=16;
r0=r0-r3;
r0=r3+(r0>>1);
r0>>=2;
return(r0);
}
假设我没有犯任何错误:
00000000 <udiv7>:
0: 4b04 ldr r3, [pc, #16]
2: 4343 muls r3, r0
4: 0c1b lsrs r3, r3, #16
6: 1ac0 subs r0, r0, r3
8: 0840 lsrs r0, r0, #1
a: 18c0 adds r0, r0, r3
c: 0883 lsrs r3, r0, #2
e: b298 uxth r0, r3
10: 4770 bx lr
12: 46c0 nop
14: 00002493 .word 0x00002493
这应该比通用的除法库例程更快。
编辑
我想我知道supercat在解决方案中所做的事情:
((i*37449 + 16384u) >> 18)
我们将其表示为1/7的分数:
0.001001001001001001001001001001
但我们只能进行32 = 32x32位乘法。前导零给了我们一些余地,我们可能可以利用它。因此,我们可以尝试使用0x2492/0x2493代替:
1001001001001001
0x9249
0x9249*0xFFFF = 0x92486db7
到目前为止,它不会溢出:
rb=((ra*0x9249) >> 18);
单独运行时,它在 7 * 0x9249 = 0x3FFFF 处失败,0x3FFFF>>18 是零而不是一。
所以也许
rb=((ra*0x924A) >> 18);
失败于:
0xAAAD 0x1862 0x1861
那么关于这个问题呢:
rb=((ra*0x9249 + 0x8000) >> 18);
而且那个有效。
超级猫的呢?
rb=((ra*0x9249 + 0x4000) >> 18);
并且对于所有的0x0000到0xFFFF之间的值都能够干净地运行:
rb=((ra*0x9249 + 0x2000) >> 18);
这里失败了:
0xE007 0x2000 0x2001
所以有几个可行的解决方案。
unsigned short udiv7 ( unsigned short x )
{
unsigned int ret
ret=x
ret=((ret*0x9249 + 0x4000) >> 18)
return(ret)
}
00000000 <udiv7>:
0: 4b03 ldr r3, [pc, #12]
2: 4358 muls r0, r3
4: 2380 movs r3, #128
6: 01db lsls r3, r3, #7
8: 469c mov ip, r3
a: 4460 add r0, ip
c: 0c80 lsrs r0, r0, #18
e: 4770 bx lr
10: 00009249 .word 0x00009249
编辑
关于“为什么”的问题,这不是一个适合在Stack Overflow上提问的问题;如果你想知道为什么gcc不这样做,请问那些代码的作者。我们能做的只是在这里推测,他们可能选择不这样做是因为指令数量太多,或者他们可能选择不这样做是因为他们有一个算法,规定如果这不是64=32x32位乘法,则不要费心。
再次强调,“为什么”问题不是Stack Overflow上的问题,所以也许我们应该关闭这个问题并删除所有答案。
我发现这非常有教育意义(一旦你知道/理解了说的是什么)。
另一个“为什么”问题是,为什么gcc要以他们的方式来做,而不是像supercat或我一样来做呢?
x*9363
时,gcc使用常量的加载后跟乘法,但是当使用x*37449
时,它选择生成一个由移动、加法和移位组成的八条指令序列。在具有缓慢乘法的ARM上可能是一个不错的权衡,但在具有快速乘法的ARM上则不是。 - supercat