【问题标题】:Alternative to using % operator and / Operator in C++在 C++ 中使用 % 运算符和 / 运算符的替代方案
【发布时间】:2011-12-29 06:02:47
【问题描述】:

据说模运算符“%”和除运算符“/”在嵌入式C++中效率很低。

我怎样才能实现以下表达式:

a = b % c;

我知道这可以使用以下逻辑来实现:

a = b - c;
while (a >= c) {
  a = a - c;
}

但我的问题是,与 % 运算符相比,这段涉及 while 循环的代码是否足够高效?

谢谢, 基尔提

【问题讨论】:

  • "与 % 运算符相比,这段涉及 while 循环的代码是否足够高效?"您告诉我们,您是使用该程序的人。是不是感觉很慢?你能注意到吗?您是否已分析并发现这很慢?
  • 这取决于大小。如果b = 1000000000c = 3。这需要一段时间......
  • 你能告诉目标CPU和编译器吗?没有它,就不可能比较任何方法。
  • 您可能会尝试分析不同的版本以查看更适合您的环境的版本。你试过吗?我认为在大多数情况下,更多的百分比是相当有效的。
  • 既然你有两个版本的实现这个,为什么不只是在你的环境中分析并自己获得结果,这些将比任何答案更具决定性,这将做出明智的猜测或近似关于你的环境。

标签: c++ modulo processing-efficiency


【解决方案1】:

没有什么比% 运算符更有效了。如果有更好的方法来做到这一点,那么任何合理的编译器都会自动转换它。当您被告知 %/ 效率低下时,那只是因为这些操作很困难 - 如果您需要执行取模,那么就这样做。

当有更好的方法时,可能会有特殊情况 - 例如,mod 2 的幂可以写成二进制文件,或者 - 但这些可能已由您的编译器优化。

【讨论】:

    【解决方案2】:

    除法和模数确实是昂贵的硬件操作,无论你做什么(这与硬件架构的关系比语言或编译器更重要),可能比加法慢十倍。

    但是,在当前的笔记本电脑或服务器以及高端微控制器上,cache 未命中通常比除法慢得多!

    当除数是常数时,GCC 编译器通常能够优化它们。

    您的幼稚循环通常比使用硬件除法指令(或执行此操作的库例程,如果硬件不提供)慢得多。我相信您在避免除法并将其替换为循环时是错误的。

    你可以调整你的算法——例如通过具有二分之一的力量-但我不建议使用您的代码。请记住过早的优化是邪恶的,所以首先尝试让您的程序正确,然后对其进行分析以找出问题所在。

    【讨论】:

    • +1 用于在担心优化之前让程序正确。许多项目失败的原因。
    • 完整时引用更好:我们应该忘记小的效率,比如大约 97% 的时间:过早的优化是万恶之源,否则很好回答。
    【解决方案3】:

    该代码几乎肯定会比您的处理器/编译器决定执行除法/修改的速度慢。一般来说,基本算术运算符很难找到快捷方式,因为 mcu/cpu 设计人员和编译器程序员非常擅长为几乎所有应用程序优化它。

    嵌入式设备中的一个常见快捷方式(每个周期/字节都可以产生影响)是将所有内容保持为 base-2 以使用位移运算符执行乘法和除法,并使用按位和 (&) 来执行乘法和除法执行模数。

    例子:

    unsigned int x = 100;
    unsigned int y1 = x << 4;   // same as x * 2^4 = x*16
    unsigned int y2 = x >> 6;   // same as x / 2^6 = x/64
    unsigned int y3 = x & 0x07; // same as x % 8
    

    【讨论】:

    • 当正确的操作数是二的幂常数时,任何体面的编译器都会为您进行相同的优化。位移/屏蔽技巧是早期编译器优化很糟糕的遗留问题,应该不再需要。
    • 不幸的是,在嵌入式世界中,您并不总是拥有像样的编译器...我同意一般情况,但是如果有疑问,快速检查反汇编将确定这是否会不会有帮助。
    【解决方案4】:

    如果除数在编译时已知,则可以将运算转换为乘以倒数,并进行一些移位、加法和其他快速运算。这在任何现代处理器上都会更快,即使它实现了硬件划分。嵌入式目标通常具有高度优化的除法/取模例程,因为标准要求这些操作。

    【讨论】:

      【解决方案5】:

      如果是 2 的幂,则可以通过移位来实现除以常数,也可以通过 mul 添加移位组合来实现。

      http://masm32.com/board/index.php?topic=9937.0 有 x86 汇编版本以及从第一篇文章下载的 C 源代码。为您生成此代码。

      【讨论】:

        【解决方案6】:

        如果您仔细分析了您的代码并发现模运算符是内部循环中的主要成本,那么有一个优化可能会有所帮助。您可能已经熟悉使用算术左移(对于 32 位值)确定整数符号的技巧:

        sign = ( x >> 31 ) | 1;
        

        这会将符号位扩展到整个字,因此负值产生 -1 和正值 0。然后设置位 0 以便正值产生 1。

        如果我们只是将值增加一个小于模的数量,那么可以使用相同的技巧来包装结果:

        val += inc;
        val -= modulo & ( static_cast< int32_t >( ( ( modulo - 1 ) - val ) ) >> 31 );
        

        或者,如果您按小于模的值递减,则相关代码为:

        int32_t signedVal = static_cast< int32_t >( val - dec );
        val = signedVal + ( modulo & ( signedVal >> 31 ) );
        

        我添加了 static_cast 运算符,因为我传入了 uint32_t,但您可能觉得它们没有必要。

        与简单的 % 运算符相比,这是否有帮助?这取决于您的编译器和 CPU 架构。我发现在 VS2012 下编译时,我的 i3 处理器上的简单循环运行速度提高了 60%,但是在 Raspberry Pi 的 ARM11 芯片上并使用 GCC 编译时,我只得到了 20% 的改进。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-01-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多