5

你可以这样写:

int i = 3;
int k = 2;
int division = i / k;
int remainder = i % k;

似乎认为这会在低层次上要求 ALU 执行两种视觉操作:一种返回商,另一种返回余数。但是,我相信 ALU 很可能会在一次操作中计算两者。如果是这种情况,这不是最有效的。

有没有更有效的方法,而不要求 CPU 计算两次?换句话说,它可以在 C++ 的单个操作中完成吗?

4

5 回答 5

9

实际上,您编写的代码不会生成任何除法指令,因为编译器可以在编译时计算出结果。我编写了一个小测试程序并设置编译器(VC++ 10SP1)以生成汇编代码列表。

#include <iostream>

using namespace std;

struct result {
    long quotient, remainder;
};

result divide(long num, long den) {
    result d = { num / den, num % den };
    return d;
}

int main() {
    result d = divide(3, 2);
    d = divide(10, 3);
    cout << d.quotient << " : " << d.remainder << endl;
    return 0;
}

我必须以这种方式编写它并明确告诉编译器不要内联任何函数。否则编译器会很高兴地优化掉大部分代码。这是生成的除法汇编代码。

; 8    : result divide(long num, long den) {

  00000 55       push    ebp
  00001 8b ec        mov     ebp, esp

; 9    :     result d = { num / den, num % den };

  00003 99       cdq
  00004 f7 7d 08     idiv    DWORD PTR _den$[ebp]

; 10   :     return d;
; 11   : }

  00007 5d       pop     ebp
  00008 c3       ret     0

生成单个 IDIV 指令并使用它生成的商和余数是足够聪明的。现代 C 和 C++ 编译器已经非常擅长这种优化。除非您有性能问题并且已经分析了您的代码以确定瓶颈在哪里,否则不要尝试再次猜测编译器。

于 2011-09-14T23:41:43.850 回答
5

当然:

int i = 3;
int k = 2;
int division = i / k;
int remainder = i - division * k;

另外,如果您真的想这样做,请查看div,但我怀疑它会更快,就像我上面的解决方案一样。

于 2011-09-14T22:48:32.697 回答
5

ISO C99具有以下ldiv功能:

#include <stdlib.h>

ldiv_t ldiv(long numer, long denom);

ldiv() 函数计算数值 numer/denom(分子/分母)。
它返回名为 ldiv_t 的结构中的商和余数,该结构包含
两个名为 quot 和 rem 的长成员。

是否在 FPU 级别减少到单个操作我不能说。

于 2011-09-14T22:50:46.470 回答
1

我不知道任何内置的东西,但你可以用乘法而不是除法来模拟它:

int division = i / k;
int remainder = i - (division * k);
于 2011-09-14T22:50:23.070 回答
0

当您问自己,什么是最快的时,通常最好对其进行基准测试(我喜欢这样做)。所以我从这里得到答案并编写了一个小型基准测试程序并将其扔到(我认为gcc会得到类似的结果) (在他优化了所有东西并且我的基准测试被打破)。g++-O0-O1

我在笔记本电脑上执行了2^28运行(从to运行)i并获得了以下运行时:k12^14

division = 0;
remainder = 0;
// this test is only there to measure the constant overhead!

1.676s

division = i/k;
remainder = i%k;

24.614s

division = i/k;
remainder = i - division*k;

15.009 秒

ldiv_t d = ldiv(i,k);
division = d.quot;
remainder = d.rem;

18.845s

正如人们所看到的,这有区别的,你最好的方法是乘法。该ldiv方法也可以,但与其他方法相比,我发现它有点麻烦。

于 2011-09-15T00:03:43.937 回答