11

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

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

a = b % c;

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

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

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

谢谢, 基尔蒂

4

6 回答 6

18

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

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

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

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

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

于 2011-11-15T06:16:08.240 回答
7

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

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

于 2011-11-15T06:15:44.680 回答
6

该代码几乎肯定会比您的处理器/编译器决定执行除法/修改的速度慢。一般来说,基本算术运算符很难找到捷径,因为 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
于 2011-11-15T06:18:41.113 回答
1

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

于 2011-11-15T06:24:44.710 回答
1

如果您仔细分析了代码并发现模运算符是内部循环中的主要成本,那么有一个优化可能会有所帮助。您可能已经熟悉使用算术左移(对于 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% 的改进。

于 2013-05-08T09:37:52.500 回答
0

如果 2 的幂或 mul 为其他人添加移位组合,则可以通过移位来实现除以常数。

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

于 2011-11-15T06:42:15.513 回答