优化后续调用整数除法和模(余数)

时间:2013-04-09 21:12:13

标签: c optimization compiler-construction compiler-optimization

整数除法/和模%运算通常在编程中一起使用,有时甚至在相同的操作数和后续行中使用。例如,以下C函数是一个简单函数,它将/个2个数字的结果与其%的结果相加,就是这样:

int sum2digits(int x, int base) {
    int n, m;
    n = x / base;
    m = x % base;
    return n + m;
}

据我所知,/%都是由同一台机器指令执行的(在x86中)。比如说,如果你对两个数字dividiv的整数除法(ab)执行机器指令,那么之后的值为a / b将存储在寄存器EAX和EDX中的余数a % b中 我想知道编译器是否利用了这种质量并看了一下汇编代码。事实证明,使用gcc进行正常编译并不能优化:

push   %rbp
mov    %rsp,%rbp
mov    %edi,-0x14(%rbp)
mov    %esi,-0x18(%rbp)
mov    -0x14(%rbp),%eax
mov    %eax,%edx
sar    $0x1f,%edx
idivl  -0x18(%rbp)
mov    %eax,-0x8(%rbp)
mov    -0x14(%rbp),%eax
mov    %eax,%edx
sar    $0x1f,%edx
idivl  -0x18(%rbp)
mov    %edx,-0x4(%rbp)
mov    -0x4(%rbp),%eax
mov    -0x8(%rbp),%edx
add    %edx,%eax
pop    %rbp
retq   

这个汇编代码会对idivl进行2次后续调用,但每次都会从另一个寄存器中读取结果(EAX代表商,EDX代表余数)。 但是,使用-O进行编译会更改图片:

mov    %edi,%eax
mov    %edi,%edx
sar    $0x1f,%edx
idiv   %esi
add    %edx,%eax
retq  

此代码仅调用idiv一次,并将其值用于两次计算 为什么这种优化不是默认的?连续两次调用div有什么用?这种优化能否以任何方式改变程序的行为? 另外,也许更重要的是,作为程序员,有没有办法手动提取这两个值(商和余数),保证CPU只执行1个整数除法?

3 个答案:

答案 0 :(得分:3)

  

为什么这种优化不是默认的?

如果编译器和优化器是完美的并且调试器可以对代码进行反向工程,那么优化将是一个普遍的默认设置。但是编译器并不总是生成正确的代码,优化器并不总是保留语义,并且调试器不能总是弄清楚任何给定指令所涉及的优化程序的哪个部分。看起来您的编译器安装了默认选项,用于绝对安全和简单调试。

  

有没有办法手动提取这2个值(商和余数),保证CPU只执行1个整数除法?

这些天最好的方法正是你所做的:向编译器询问优化代码。 div例程是从分割运算符的结果为负数实现定义的日子开始的,而优化编译的速度非常慢,因此最好手动识别这样的事情。

答案 1 :(得分:2)

您可以随时实施自己的部门:

#include <stdlib.h>
#include <stdio.h>

void mydiv(int dividend, int divisor, int* quotient, int* remainder)
{
  *quotient = dividend / divisor;
  *remainder = dividend - *quotient * divisor;
}

int testData[][2] =
{
  { +5, +3 },
  { +5, -3 },
  { -5, +3 },
  { -5, -3 },
};

int main(void)
{
  unsigned i;
  for (i = 0; i < sizeof(testData)/sizeof(testData[0]); i++)
  {
    div_t res1, res2;
    res1 = div(testData[i][0], testData[i][1]);
    mydiv(testData[i][0], testData[i][1], &res2.quot, &res2.rem);
    printf("%+d/%+d = %+d:%+d %c= %+d:%+d\n",
           testData[i][0], testData[i][1],
           res1.quot, res1.rem,
           "!="[res1.quot == res2.quot && res1.rem == res2.rem],
           res2.quot, res2.rem);
  }
  return 0;
}

输出(ideone):

+5/+3 = +1:+2 == +1:+2
+5/-3 = -1:+2 == -1:+2
-5/+3 = -1:-2 == -1:-2
-5/-3 = +1:-2 == +1:-2

这确实有一个部门。但是,看起来gcc不够聪明,无法消除乘法,所以你有一个。

答案 2 :(得分:1)

为什么不使用div?

http://www.cplusplus.com/reference/cstdlib/div/

我认为这是优化便携式解决方案的最佳机会吗?

对于那些拒绝了我的回答的人:请不要删除这个答案!如果你必须投票,或评论为什么它是如此错误,你认为它需要被删除。

OP希望了解有关整数除法的后续调用和使用C的模数(余数)的优化。

因此,如果您希望在代码中保持最佳状态,为什么不使用标准库调用将优化的责任推迟到标准库实现者,这些实现者可能拥有编译器内部工作的更好信息以及本机的可用操作机器(即在x86上使用div汇编指令)。特别是当该功能完全符合OP试图做的事情时。

如果我在实际代码中看到一个除法后跟一个mod,我的直接问题是“你为什么不使用标准库?”,而不是“嗯,我想知道编译器如何优化我的高 - 等级代码?“。

它也回答了问题的一部分:另外,也许更重要的是,作为程序员,有一种方法可以手动提取这两个值(商和余数),保证只执行1个整数除法。 CPU?