提问人:JeffV 提问时间:9/7/2008 最后编辑:Kirill KobelevJeffV 更新时间:6/9/2020 访问量:60326
除了在 C/C++ 中使用 %(模数)之外,还有其他选择吗?
Is there any alternative to using % (modulus) in C/C++?
问:
我曾经在某处读到,模运算符在小型嵌入式设备(如 8 位微控制器)上效率低下,这些设备没有整数除法指令。也许有人可以证实这一点,但我认为差异比整数除法运算慢 5-10 倍。
除了保留计数器变量并在 mod 点手动溢出到 0 之外,还有其他方法可以做到这一点吗?
const int FIZZ = 6;
for(int x = 0; x < MAXCOUNT; x++)
{
if(!(x % FIZZ)) print("Fizz\n"); // slow on some systems
}
与:
我目前的做法:
const int FIZZ = 6;
int fizzcount = 1;
for(int x = 1; x < MAXCOUNT; x++)
{
if(fizzcount >= FIZZ)
{
print("Fizz\n");
fizzcount = 0;
}
}
答:
在嵌入式世界中,您需要执行的“模数”操作通常可以很好地分解为可以使用的位操作,有时还可以执行。&
|
>>
评论
除非您确实需要在多个嵌入式平台上实现高性能,否则在进行分析之前,不要出于性能原因更改编码方式!
为优化性能而笨拙编写的代码很难调试和维护。编写一个测试用例,并在目标上对其进行分析。一旦您知道模量的实际成本,然后决定替代解决方案是否值得编码。
评论
并不是说这一定更好,但你可以有一个总是上升到 的内循环,以及一个重复一定次数的外循环。然后,如果不能被 整除,则最后几步可能必须得到特殊情况。FIZZ
MAXCOUNT
FIZZ
也就是说,我建议对你的目标平台进行一些研究和性能分析,以清楚地了解你所受到的性能限制。可能有更高效的地方可以花费您的优化工作。
您是否可以访问嵌入式设备上的任何可编程硬件?喜欢计数器之类的?如果是这样,您也许可以编写基于硬件的 mod 单元,而不是使用模拟的 %。(我在VHDL中做过一次。不确定我是否还有代码。
请注意,您确实说过除法速度快 5-10 倍。你有没有考虑过做一个除法、乘法和减法来模拟这个模组?(编辑:误解了原来的帖子。我确实觉得除法比mod快很奇怪,它们是相同的操作。
但是,在您的特定情况下,您正在检查 6 的模组。6 = 2*3。因此,如果您首先检查最低有效位是否为 0,您可能会获得一些小的收益。像这样:
if((!(x & 1)) && (x % 3))
{
print("Fizz\n");
}
不过,如果你这样做,我建议确认你得到了任何收益,对分析器来说太好了。并做一些评论。我会为下一个必须查看代码的人感到难过。
评论
如果您正在计算一个数字 mod 的 2 的幂,则可以使用按位 和 运算符。只需从第二个数字中减去 1。例如:
x % 8 == x & 7
x % 256 == x & 255
需要注意的几点:
- 仅当第二个数字是 2 的幂时,这才有效。
- 只有当模量始终为正时,它才是等价的。当第一个数字为负数时,C 和 C++ 标准没有指定模数的符号(直到 C++11,这确实保证它是负数,这是大多数编译器已经在做的事情)。逐位并去掉符号位,因此它始终为正数(即它是真正的模数,而不是余数)。不过,这听起来像是你想要的。
- 你的编译器可能已经在可以的时候这样做了,所以在大多数情况下,不值得手动这样做。
评论
@Matthew是对的。试试这个:
int main() {
int i;
for(i = 0; i<=1024; i++) {
if (!(i & 0xFF)) printf("& i = %d\n", i);
if (!(i % 0x100)) printf("mod i = %d\n", i);
}
}
@Jeff V:我看出它有问题!(除此之外,您的原始代码正在寻找 mod 6,现在您基本上正在寻找 mod 8)。你继续做额外的 +1!希望您的编译器能够优化它,但为什么不直接从 2 开始测试并转到 MAXCOUNT(包括 MAXCOUNT)呢?最后,每次 (x+1) 不能被 8 整除时,您都会返回 true。这是你想要的吗?(我假设是,但只是想确认一下。
评论
I'm looking for an answer that could be used for any number known [at compile time].
编辑有助于在问题正文中有效回答问题的信息!(您可能知道 14 年&50k 代表后......
您确实应该检查所需的嵌入式设备。我见过的所有汇编语言(x86、68000)都使用除法来实现模数。
实际上,除法装配操作在两个不同的寄存器中返回除法的结果和剩余结果。
啊,按位算术的乐趣。许多除法例程的一个副作用是模数 - 因此在少数情况下,除法实际上应该比模数快。我很想看看你从哪里得到这些信息。带有乘法器的处理器使用乘法器具有有趣的除法例程,但您只需再走两步(乘法和减法)即可从除法结果获得模数,因此它仍然具有可比性。如果处理器具有内置的除法例程,您可能会看到它也提供了余数。
尽管如此,数论中还有一小部分专门用于模算术,如果您真的想了解如何优化模运算,则需要对其进行研究。例如,模块化算术对于生成幻方非常方便。
因此,本着这种精神,这里以 x 为例,对模数的数学进行了非常低层次的了解,它应该向您展示它与除法相比是多么简单:
也许思考这个问题的更好方法是从数量的角度来看 基数和模算术。例如,您的目标是计算 DOW mod 7,其中 DOW 是 16 位表示日期 周。你可以这样写:
DOW = DOW_HI*256 + DOW_LO
DOW%7 = (DOW_HI*256 + DOW_LO) % 7
= ((DOW_HI*256)%7 + (DOW_LO % 7)) %7
= ((DOW_HI%7 * 256%7) + (DOW_LO%7)) %7
= ((DOW_HI%7 * 4) + (DOW_LO%7)) %7
以这种方式表示,您可以单独计算模 7 高字节和低字节的结果。将高值的结果乘以 4 并将其添加到低值,最后计算结果模 7。
计算 8 位数字的 mod 7 结果可以在 类似的时尚。您可以像这样用八进制数写一个 8 位数字:
X = a*64 + b*8 + c
其中 a、b 和 c 是 3 位数字。
X%7 = ((a%7)*(64%7) + (b%7)*(8%7) + c%7) % 7
= (a%7 + b%7 + c%7) % 7
= (a + b + c) % 7
因为64%7 = 8%7 = 1
当然,a、b 和 c 是
c = X & 7
b = (X>>3) & 7
a = (X>>6) & 7 // (actually, a is only 2-bits).
的最大可能值是 。因此,您将需要
又一个八进制步骤。完整的(未经测试的)C 版本可以是
写成这样:a+b+c
7+7+3 = 17
unsigned char Mod7Byte(unsigned char X)
{
X = (X&7) + ((X>>3)&7) + (X>>6);
X = (X&7) + (X>>3);
return X==7 ? 0 : X;
}
我花了一些时间写了一个PIC版本。实际实施 与上述略有不同
Mod7Byte:
movwf temp1 ;
andlw 7 ;W=c
movwf temp2 ;temp2=c
rlncf temp1,F ;
swapf temp1,W ;W= a*8+b
andlw 0x1F
addwf temp2,W ;W= a*8+b+c
movwf temp2 ;temp2 is now a 6-bit number
andlw 0x38 ;get the high 3 bits == a'
xorwf temp2,F ;temp2 now has the 3 low bits == b'
rlncf WREG,F ;shift the high bits right 4
swapf WREG,F ;
addwf temp2,W ;W = a' + b'
; at this point, W is between 0 and 10
addlw -7
bc Mod7Byte_L2
Mod7Byte_L1:
addlw 7
Mod7Byte_L2:
return
下面是测试算法的 liitle 例程
clrf x
clrf count
TestLoop:
movf x,W
RCALL Mod7Byte
cpfseq count
bra fail
incf count,W
xorlw 7
skpz
xorlw 7
movwf count
incfsz x,F
bra TestLoop
passed:
最后,对于 16 位结果(我尚未测试),您可以 写:
uint16 Mod7Word(uint16 X)
{
return Mod7Byte(Mod7Byte(X & 0xff) + Mod7Byte(X>>8)*4);
}
斯科特
评论
print 语句所需的时间甚至比模运算符的最慢实现要长几个数量级。因此,基本上“在某些系统上速度慢”的评论应该是“在所有系统上速度慢”。
此外,提供的两个代码片段不会执行相同的操作。在第二条中,线
if(fizzcount >= FIZZ)
始终为 false,因此从不打印“FIZZ\n”。
大多数时候,使用不是 2 的幂的模会产生开销。 这与处理器无关,因为 (AFAIK) 即使是带有模运算符的处理器,除法操作也比掩码操作慢几个周期。
在大多数情况下,这不是一个值得考虑的优化,当然也不值得计算你自己的快捷方式运算(特别是如果它仍然涉及除法或乘法)。
但是,一个经验法则是选择数组大小等为 2 的幂。
因此,如果计算星期几,不妨使用 %7 如果设置一个大约 100 个条目的循环缓冲区......为什么不让它成为 128。然后你可以写 % 128,大多数(所有)编译器都会这样做 & 0x7F
x%y == (x-(x/y)*y)
希望这会有所帮助。
评论
对于模 6,您可以将 Python 代码更改为 C/C++:
def mod6(number):
while number > 7:
number = (number >> 3 << 1) + (number & 0x7)
if number > 5:
number -= 6
return number
评论
上一个:C++ 删除指向指针的指针
下一个:什么是跳台?
评论
I read somewhere once that the modulus operator is inefficient
...我相信你可能会想到奈杰尔·琼斯(Nigel Jones)关于这个主题的一篇博客文章。在这篇文章中,他比较了不同 uC 的模量计算......ARM Cortex 为 390 个周期,MSP430 和 AVR 为 30,000 个周期。