据说模操作符“%”和除法操作符“/”在嵌入式C++中效率很低。
我怎样才能实现以下表达式:
a = b % c;
我知道这可以使用以下逻辑来实现:
a = b - c;
while (a >= c) {
a = a - c;
}
但我的问题是,与 % 运算符相比,这段涉及 while 循环的代码是否足够有效?
谢谢, 基尔蒂
据说模操作符“%”和除法操作符“/”在嵌入式C++中效率很低。
我怎样才能实现以下表达式:
a = b % c;
我知道这可以使用以下逻辑来实现:
a = b - c;
while (a >= c) {
a = a - c;
}
但我的问题是,与 % 运算符相比,这段涉及 while 循环的代码是否足够有效?
谢谢, 基尔蒂
除法和模数确实是昂贵的硬件操作,无论你做什么(这与硬件架构比语言或编译器更相关),可能比加法慢十倍。
但是,在当前的笔记本电脑或服务器以及高端微控制器上,缓存未命中通常比除法慢得多!
当除数是常数时,GCC 编译器通常能够优化它们。
您的幼稚循环通常比使用硬件除法指令(或执行此操作的库例程,如果硬件未提供)慢得多。我相信你在避免分裂并用你的循环替换它是错误的。
您可能会调整您的算法 - 例如通过具有二分之一的力量 - 但我不建议使用您的代码。请记住,过早的优化是邪恶的,所以首先尝试让您的程序正确,然后对其进行分析以找出问题所在。
没有什么比%
操作员更有效率了。如果有更好的方法,那么任何合理的编译器都会自动转换它。当您被告知%
并且/
效率低下时,那只是因为这些操作很困难-如果您需要执行模数,那么就这样做。
当有更好的方法时,可能会有特殊情况——例如,mod 2 的幂可以写成二进制文件,或者——但这些可能已经被你的编译器优化了。
该代码几乎肯定会比您的处理器/编译器决定执行除法/修改的速度慢。一般来说,基本算术运算符很难找到捷径,因为 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
如果除数在编译时已知,则可以将操作转换为乘以倒数,并进行一些移位、加法和其他快速操作。这在任何现代处理器上都会更快,即使它实现了硬件划分。嵌入式目标通常具有高度优化的除法/取模例程,因为这些操作是标准要求的。
如果您仔细分析了代码并发现模运算符是内部循环中的主要成本,那么有一个优化可能会有所帮助。您可能已经熟悉使用算术左移(对于 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% 的改进。
如果 2 的幂或 mul 为其他人添加移位组合,则可以通过移位来实现除以常数。
http://masm32.com/board/index.php?topic=9937.0 有 x86 汇编版本以及从第一篇文章下载的 C 源代码。为您生成此代码。