在C / C ++中使用%(模数)有什么替代方法吗?

问题描述 投票:32回答:13

我曾经读到过某些地方模数运算符在小型嵌入式设备(例如没有整数除法指令的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
}

VS:

我目前正在这样做的方式:

const int FIZZ = 6;
int fizzcount = 1;
for(int x = 1; x < MAXCOUNT; x++)
{
    if(fizzcount >= FIZZ) 
    {
        print("Fizz\n");
        fizzcount = 0;
    }
}
c++ c modulo embedded
13个回答
47
投票

啊,按位算术的乐趣。许多除法程序的副作用是模数 - 因此在少数情况下,除法实际上应该比模数更快。我很有兴趣看到您从中获取此信息的来源。具有乘数的处理器使用乘法器具有有趣的除法例程,但是您可以通过另外两个步骤(乘法和减法)从除法结果到模数,因此它仍然具有可比性。如果处理器有一个内置的分区例程,你可能会看到它还提供剩余部分。

尽管如此,还是有一个专门针对Modular Arithmetic的数论的小分支,如果你真的想要了解如何优化模数运算,这需要研究。例如,模块化算术对于生成magic squares非常方便。

所以,在这种情况下,这里有一个q的xzxswpoi,它是x的一个例子的模数,它应该向你显示它与分区相比有多简单:


也许更好的思考问题的方法是数字基数和模运算。例如,你的目标是计算DOW mod 7,其中DOW是星期几的16位表示。你可以这样写:

very low level look

以这种方式表示,您可以分别计算高字节和低字节的模7结果。将高的结果乘以4并将其加到低,然后最终计算结果模7。

计算8位数的mod 7结果可以以类似的方式执行。您可以在八进制中写入一个8位数字,如下所示:

 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

其中a,b和c是3位数。

  X = a*64 + b*8 + c

X%7 = ((a%7)*(64%7) + (b%7)*(8%7) + c%7) % 7 = (a%7 + b%7 + c%7) % 7 = (a + b + c) % 7

当然,a,b和c是

64%7 = 8%7 = 1

c = X & 7 b = (X>>3) & 7 a = (X>>6) & 7 // (actually, a is only 2-bits). 最大的可能值是a+b+c。所以,你还需要一个八进制步骤。完整的(未经测试的)C版本可以写成:

7+7+3 = 17

我花了一些时间写一个PIC版本。实际实现与上述略有不同

unsigned char Mod7Byte(unsigned char X)
{
    X = (X&7) + ((X>>3)&7) + (X>>6);
    X = (X&7) + (X>>3);

    return X==7 ? 0 : X;
}

这是测试算法的一个小例程

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

最后,对于16位结果(我没有测试过),你可以写:

       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:

斯科特



0
投票

并不是说这一定更好,但你可以有一个内循环,它总是上升到FIZZ,一个外循环,它重复它一定次数。如果MAXCOUNT不能被FIZZ整除,那么你可能会遇到特殊情况的最后几步。

也就是说,我建议您在预期的平台上进行一些研究和性能分析,以便清楚地了解您所面临的性能限制。可能会有更高效的地方来进行优化工作。


0
投票

@Jeff V:我发现它有问题! (除此之外,您的原始代码正在寻找mod 6,现在您基本上正在寻找mod 8)。你继续做额外的+1!希望你的编译器能够优化它,但为什么不只是测试从2开始并转到MAXCOUNT包含?最后,每当(x + 1)不能被8整除时,你就会返回true。这就是你想要的吗? (我认为是,但只想确认。)


0
投票

对于模6,您可以将Python代码更改为C / C ++:

if((!(x & 1)) && (x % 3))
{
    print("Fizz\n");
}

-4
投票

print语句比模数运算符的最慢实现要长几个数量级。所以基本上评论“在一些系统上慢”应该“在所有系统上都很慢”。

此外,提供的两个代码片段不会做同样的事情。在第二个,行

if(fizzcount >= FIZZ)

永远是假的,所以永远不会打印“FIZZ \ n”。


35
投票

如果你正在计算一个数字mod一些2的幂,你可以使用逐位和运算符。只需从第二个数字中减去一个。例如:

uint16 Mod7Word(uint16 X)
{
 return Mod7Byte(Mod7Byte(X & 0xff) + Mod7Byte(X>>8)*4);
}

一些警告:

  1. 这仅在第二个数字是2的幂时才有效。
  2. 如果模数总是正的,它只是等价的。当第一个数字为负时,C和C ++标准没有指定模数的符号(直到C ++ 11,它确保它是负数,这是大多数编译器已经在做的)。按位并且去除符号位,因此它将始终为正(即,它是真模数,而不是余数)。听起来这就是你想要的东西。
  3. 您的编译器可能已经可以执行此操作,因此在大多数情况下,不值得手动执行此操作。

6
投票

大多数时候使用modulo的开销不是2的幂。这与处理器无关(AFAIK),即使是具有模数运算符的处理器,除了掩码运算之外,分频的周期也要慢几个周期。

对于大多数情况,这不是一个值得考虑的优化,当然不值得计算自己的快捷操作(特别是如果它仍然涉及分或乘)。

但是,一条经验法则是选择数组大小等为2的幂。

因此,如果计算星期几,也可以使用%7,无论是否设置大约100个条目的循环缓冲区...为什么不将它设为128.然后你可以编写%128而大多数(所有)编译器将使这个和0x7F


4
投票

除非您确实需要在多个嵌入式平台上实现高性能,否则在配置之前不要更改性能原因的代码编码方式!

为优化性能而编写的代码难以调试且难以维护。编写测试用例,并在目标上对其进行概要分析。一旦知道了模数的实际成本,就可以确定替代解决方案是否值得编码。


3
投票

@Matthew是对的。试试这个:

x % 8 == x & 7
x % 256 == x & 255

2
投票
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);
  }
}

希望这可以帮助。


1
投票

在嵌入式世界中,您需要执行的“模数”操作通常是能够很好地分解为可以使用'&'和'|'进行的位操作的操作有时候“>>”。


1
投票

您是否可以访问嵌入式设备上的任何可编程硬件?像柜台等?如果是这样,您可能能够编写基于硬件的mod单元,而不是使用模拟的%。 (我在VHDL中做过一次。不知道我是否还有代码。)

请注意,你确实说分裂的速度要快5到10倍。您是否考虑过进行除法,乘法和减法来模拟模型? (编辑:误解了原来的帖子。我确实认为分裂比mod更快是奇怪的,它们是相同的操作。)

但是,在您的具体情况下,您正在检查6. 6 = 2 * 3的mod。因此,如果您首先检查最低有效位是否为0,那么您可能会获得一些小的收益。例如:

x%y == (x-(x/y)*y)

但是,如果你这样做,我建议确认你获得任何收益,对于剖析师而言。并做一些评论。对于下一个不得不查看代码的人来说,我会感到很难过。


1
投票

您应该检查所需的嵌入式设备。我见过的所有汇编语言(x86,68000)都使用除法来实现模数。

实际上,除法组合操作返回除法的结果,并将剩余的寄存器返回到两个不同的寄存器中。

© www.soinside.com 2019 - 2024. All rights reserved.