【问题标题】:Is there any alternative to using % (modulus) in C/C++?在 C/C++ 中使用 % (模数)有什么替代方法吗?
【发布时间】:2010-09-08 01:31:18
【问题描述】:

我曾经在某处读到,模数运算符在小型嵌入式设备上效率低下,例如没有整数除法指令的 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;
    }
}

【问题讨论】:

  • 在第二个示例中您忘记增加 fizzcount。
  • I read somewhere once that the modulus operator is inefficient... 我相信您可能会想到与此主题有关的Nigel Jones' blog posts 之一。在帖子中,他比较了不同 uC 的模数计算……ARM Cortex 为 390 个周期,MSP430 和 AVR 为近 30,000 个周期。
  • 谢谢 Dan,我很高兴我现在使用的是 ARM 皮质。

标签: c++ c modulo embedded


【解决方案1】:

并不是说这一定会更好,但是您可以有一个始终上升到FIZZ 的内部循环,以及一个重复一定次数的外部循环。如果MAXCOUNT 不能被FIZZ 整除,那么您可能需要在最后几个步骤中处理特殊情况。

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

【讨论】:

    【解决方案2】:

    在嵌入式世界中,您需要执行的“模数”运算通常可以很好地分解为位运算,您可以使用&amp;| 和有时&gt;&gt; 执行这些运算。

    【讨论】:

    • 如果不是两个数字的整数幂,您将如何处理?
    • Jeff V:关于数学的有趣事实:x * 7 == x + (x * 2) + (x * 4),或 x + x >> 1 + x >> 2。整数加法通常很便宜。
    【解决方案3】:

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

    def mod6(number):
        while number > 7:
            number = (number >> 3 << 1) + (number & 0x7)
        if number > 5:
            number -= 6
        return number
    

    【讨论】:

      【解决方案4】:
      x%y == (x-(x/y)*y)
      

      希望这会有所帮助。

      【讨论】:

      • 正是我要找的东西!!
      • Modulus 在缺少硬件除法指令的微控制器上运行缓慢。所以这实际上并不能解决问题。
      【解决方案5】:

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

      不过,如果您真的想了解如何优化模运算,则需要研究数论的一个小分支 Modular Arithmetic。例如,模块化算术对于生成magic squares 非常方便。

      因此,在这种情况下,这里有一个 very low level look 的模数数学示例,它应该向您展示它与除法相比有多么简单:


      也许考虑问题的更好方法是从数字的角度 基数和模数运算。例如,您的目标是计算 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).
      

      a+b+c 的最大可能值为7+7+3 = 17。所以,你需要 又是八进制的一步。完整的(未经测试的)C 版本可能是 写成这样:

      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
      

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

             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);
      }
      

      斯科特


      【讨论】:

      • 哇,如果我必须为 7 以外的数字做这件事,我将不得不回来重新阅读这篇文章。
      【解决方案6】:

      如果您要计算一个以 2 的幂为模的数字,则可以使用按位与运算符。只需从第二个数字中减去一个。例如:

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

      一些注意事项:

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

      【讨论】:

      【解决方案7】:

      在大多数情况下,使用不是 2 的幂的模数会产生开销。 这与处理器无关,因为(AFAIK)即使具有模数运算符的处理器在除法方面比掩码操作慢几个周期。

      在大多数情况下,这不是值得考虑的优化,当然也不值得计算您自己的快捷操作(特别是如果它仍然涉及除法或乘法)。

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

      所以如果计算星期几,不妨使用 %7 不管 如果设置大约 100 个条目的循环缓冲区...为什么不将其设置为 128。然后您可以编写 % 128 并且大多数(所有)编译器都会使这个 & 0x7F

      【讨论】:

        【解决方案8】:

        打印语句所花费的时间甚至比模运算符的最慢实现要长几个数量级。所以基本上“在某些系统上慢”的评论应该是“在所有系统上都很慢”。

        另外,提供的两个代码 sn-ps 不做同样的事情。在第二个中,行

        if(fizzcount >= FIZZ)

        始终为 false,因此永远不会打印“FIZZ\n”。

        【讨论】:

          【解决方案9】:

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

          实际上,除法汇编操作将除法的结果和剩余的结果放在两个不同的寄存器中。

          【讨论】:

            【解决方案10】:

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

            【讨论】:

            • 是的,我也看到了。我将其更改为 7,因为 6 可以被 2 整除。我正在寻找一个可以用于任何预先知道的数字的答案。
            【解决方案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);
              }
            }
            

            【讨论】:

              【解决方案12】:

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

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

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

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

              但是,如果您这样做,我建议您确认您获得了任何收益,对分析器来说是这样。并做一些评论。我会为下一个必须查看代码的人感到难过。

              【讨论】:

              • 我的意思是在指令集中使用整数除法操作码的微控制器上,除法和模数要快 5-10 倍。
              • 哦,对不起,我误会了。 (显然。)你有没有介绍过其他建议?
              【解决方案13】:

              除非您真的需要在多个嵌入式平台上实现高性能,否则在您进行分析之前,不要出于性能原因更改您的编码方式!

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

              【讨论】:

              • 我 100% 同意您的观点,但是,在性能很重要的情况下,您是否有替代模数的方法?
              • 我没有具体的选择。如果我不能像@Paul-Tomblin 建议的那样使用位掩码或右移,我会保留一个计数器,就像你在问题中建议的那样。当然,我习惯于处理更多开销(我们在处理器上运行 CORBA)。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2015-04-09
              • 2021-08-20
              • 2017-03-02
              • 2011-01-15
              • 2012-01-27
              • 1970-01-01
              • 2023-02-10
              相关资源
              最近更新 更多