我正在为一个非常有限的系统编写一些代码,其中mod运算符非常慢。在我的代码中,模数需要每秒使用大约180次,并且我认为尽可能地删除它会显着提高代码的速度,因为现在我的主循环的一个循环不会在1/60的情况下运行应该是第二个。我想知道是否有可能仅使用乘法和除法可能的位移来重新实现模数。所以这是我目前在c ++中的代码(如果我可以使用汇编执行模数,那就更好了)。如何在不使用除法或乘法的情况下删除模数?
while(input > 0)
{
out = (out << 3) + (out << 1);
out += input % 10;
input = (input >> 8) + (input >> 1);
}
编辑:实际上我意识到我需要每秒执行180次以上。看作输入的值可以是一个非常大的数字,最多40位数。
答案 0 :(得分:15)
使用简单按位运算可以做的是通过将它与除数-1进行AND运算,取值(除数)的二次幂(除数)。几个例子:
unsigned int val = 123; // initial value
unsigned int rem;
rem = val & 0x3; // remainder after value is divided by 4.
// Equivalent to 'val % 4'
rem = val % 5; // remainder after value is divided by 5.
// Because 5 isn't power of two, we can't simply AND it with 5-1(=4).
为什么会这样?让我们考虑值123的位模式,即1111011
,然后是除数4,其位模式为00000100
。正如我们现在知道的那样,除数必须是2的幂(如4),我们需要将它递减1(从4到3的十进制),这就产生了位模式00000011
。在我们对原始的123和3进行按位与AND之后,得到的位模式将为00000011
。结果是十进制的3。我们需要二次幂除数的原因是,一旦我们将它们递减一,我们将所有不太重要的位设置为1
,其余为0
。一旦我们按位进行AND运算,它就会“取消”原始值中更重要的位,并简单地将原始值的剩余部分除以除数。
然而,除非你事先知道你的除数(在编译时,甚至那时需要除数特定的代码路径),否则对任意除数应用这样的特定事物是行不通的 - 解析它的运行时是不可行的,尤其不是在你的表现很重要的情况下。
此外还有a previous question related to the subject从不同的角度来看可能有关于这个问题的有趣信息。
答案 1 :(得分:2)
使用位移进行模10会变得困难和丑陋,因为位移本质上是二进制的(在今天你将要运行的任何机器上)。如果你考虑一下,位移只是乘以或除以2。
但是您可以在此处进行明显的时空交易:为out
和out % 10
设置一个值表并进行查找。然后该行成为
out += tab[out]
如果运气好的话,那将是一个16位的添加和商店操作。
答案 2 :(得分:2)
实际上,常量除法是众所周知的编译器优化,事实上,gcc已经在做了。
这个简单的代码段:
int mod(int val) {
return val % 10;
}
使用-O3:
在我相当老的gcc上生成以下代码_mod:
push ebp
mov edx, 1717986919
mov ebp, esp
mov ecx, DWORD PTR [ebp+8]
pop ebp
mov eax, ecx
imul edx
mov eax, ecx
sar eax, 31
sar edx, 2
sub edx, eax
lea eax, [edx+edx*4]
mov edx, ecx
add eax, eax
sub edx, eax
mov eax, edx
ret
如果你忽略了函数epilogue / prologue,基本上是两个muls(确实在x86上我们很幸运,可以使用lea一个)和一些移位和添加/ subs。我知道我已经在某处解释了这种优化背后的理论,所以我会在再次解释之前看看是否可以找到该帖子。
现在在现代CPU上,这肯定比访问内存更快(即使你点击了缓存),但是它是否更快,显然更古老的CPU是一个问题,只能通过基准测试来回答(并确保你的编译器正在进行优化,否则你总是可以在这里“窃取”gcc版本;))。特别是考虑到它依赖于有效的mulhs(即乘法指令的较高位)是有效的。 请注意,此代码不大小独立 - 确切地说是幻数变化(也可能是添加/移位的一部分),但可以进行调整。
答案 3 :(得分:1)
如果你想做模10和转换,也许你可以根据自己的需要调整 double dabble algorithm ?
此算法用于将二进制数转换为十进制数而不使用模数或除法。
答案 4 :(得分:1)
每个16的幂都以6结尾。如果你将数字表示为16的幂之和(即将其分解为nybbles),那么每个术语以相同的方式对最后一个数字做出贡献,除了一个位置。 / p>
0x481A % 10 = ( 0x4 * 6 + 0x8 * 6 + 0x1 * 6 + 0xA ) % 10
注意6 = 5 + 1,如果有偶数,5将取消。所以只需对nybbles(除了最后一个)求和,如果结果是奇数,则加5.
0x481A % 10 = ( 0x4 + 0x8 + 0x1 /* sum = 13 */
+ 5 /* so add 5 */ + 0xA /* and the one's place */ ) % 10
= 28 % 10
这会将16位,4-nybble模数减少到最多0xF * 4 + 5 = 65
的数字。在二进制文件中,令人烦恼的仍然是3个nybbles,所以你需要重复算法(尽管其中一个算法并不算数)。
但是286应该具有合理有效的BCD添加,您可以使用它来执行总和并在一次通过中获得结果。 (这需要手动将每个nybble转换为BCD;我不太了解该平台如何优化它或是否存在问题。)