在不同的数组中划分和存储商和提醒

时间:2016-01-24 16:08:40

标签: c

标准div()函数返回div_t结构作为参数,例如:

/* div example */
#include <stdio.h>      /* printf */
#include <stdlib.h>     /* div, div_t */

int main ()
{
  div_t divresult;
  divresult = div (38,5);
  printf ("38 div 5 => %d, remainder %d.\n", divresult.quot, divresult.rem);
  return 0;
}

我的情况有点不同;我有这个

#define NUM_ELTS 21433
int main ()
{
  unsigned int quotients[NUM_ELTS];
  unsigned int remainders[NUM_ELTS];
  int i;

  for(i=0;i<NUM_ELTS;i++) {
      divide_single_instruction(&quotient[i],&reminder[i]);
  }
}

我知道除法的汇编语言在单指令中完成所有操作,所以我需要在这里做同样的事情以节省cpu周期,这就是将来自EAX的商和提示从EDX转移到我的数组的内存位置存储。如果不在我的C代码中包含asm {}或SSE内在函数,怎么做呢?它必须是便携式的。

2 个答案:

答案 0 :(得分:1)

由于你在原地写入数组(用商和余数替换分子和分母),你应该在写入数组之前将结果存储到临时变量。

void foo (unsigned *num, unsigned *den, int n) {
    int i;
    for(i=0;i<n;i++) {
        unsigned q = num[i]/den[i], r = num[i]%den[i];   
        num[i] = q, den[i] = r;
    }
}

生成这个主循环程序集

.L5:
        movl    (%rdi,%rcx,4), %eax
        xorl    %edx, %edx
        divl    (%rsi,%rcx,4)
        movl    %eax, (%rdi,%rcx,4)
        movl    %edx, (%rsi,%rcx,4)
        addq    $1, %rcx
        cmpl    %ecx, %r8d
        jg      .L5

有一些更复杂的情况,它有助于在第一次使用时保存商和余数。例如,在通过试验部门测试质数时,您经常会看到像这样的循环

for (p = 3; p <= n/p; p += 2)
    if (!(n % p)) return 0;

事实证明GCC does not use the remainder from the first division因此它执行两次分割指令是不必要的。要解决此问题,您可以在第一次除法时保存余数:

for (p = 3, q=n/p, r=n%p; p <= q; p += 2, q = n/p, r=n%p)
    if (!r) return 0;

这将结果加速了两倍。

所以一般来说GCC做得很好,特别是如果你在第一次计算它们时保存商和余数。

答案 1 :(得分:0)

这里的一般规则是信任编译器快速执行某些操作。您始终可以反汇编代码并检查编译器是否正在做一些正确的事情。重要的是要认识到一个优秀的编译器对机器有很多认识,通常比你或我更多。

另外,让我们假设你有充分的理由需要&#34;计算周期&#34;。

对于您的示例代码,我同意x86&#34; idiv&#34;教学是显而易见的选择。如果我只写出最天真的代码,那么让我们看看我的编译器(MS visual C 2013)会做什么

struct divresult {
    int quot;
    int rem;
};

struct divresult divrem(int num, int den)
{
    return (struct divresult) { num / den, num % den };
}

int main()
{
    struct divresult res = divrem(5, 2);
    printf("%d, %d", res.quot, res.rem);
}

编译器给了我们:

    struct divresult res = divrem(5, 2);
    printf("%d, %d", res.quot, res.rem);
01121000  push        1 
01121002  push        2
01121004  push        1123018h  
01121009  call        dword ptr ds:[1122090h] ;;; this is printf()
哇,我被编译器打败了。 Visual C知道除法是如何工作的,因此它只是预先计算结果并插入常量。它甚至没有把我的功能包含在最终代码中。我们必须从控制台读取整数以强制它实际进行计算:

int main()
{
    int num, den;
    scanf("%d, %d", &num, &den);
    struct divresult res = divrem(num, den);
    printf("%d, %d", res.quot, res.rem);
}

现在我们得到:

    struct divresult res = divrem(num, den);
01071023  mov         eax,dword ptr [num]  
01071026  cdq  
01071027  idiv        eax,dword ptr [den]  
    printf("%d, %d", res.quot, res.rem);
0107102A  push        edx  
0107102B  push        eax  
0107102C  push        1073020h  
01071031  call        dword ptr ds:[1072090h] ;;; printf()

所以你看,编译器(或至少这个编译器)已经做了你想做的事情,或者更聪明的事情。

由此我们学会信任编译器,只有当我们知道它已经做得不够好时才进行第二次猜测。