阶乘算法优化

__attribute__((noinline))
int test(int n)
{
        int fact = 1, num = n + 1;
        int i =1;
        for (i = 1; i < num; i++) {
                fact *= i;
        }
        return fact;
}
int main()
{
        printf("%d\n", test(1000000000));
}

为方便分析,函数calc()前面加上__attribute__((noinline)),禁止GCC把calc内联在main()中。此外,calc()中,fact类型是int,main()中调用calc(1000000000),会导致fact溢出,但不影响测试,不用管它。

编译一下,然后用time命令测量下运行耗时:

[root@localhost ~]# gcc jiecheng.c -o jiecheng
[root@localhost ~]# time ./jiecheng
0

real    0m25.159s
user    0m25.160s
sys     0m0.000s

用时25s

#include <stdio.h>
int test(int n)
{
        int fact0 = 1,fact1 = 1,fact2 = 1,fact3 = 1;
        int i =1;
        for (i = 1; i < n; i+=4) {
                fact0 *= i;
                fact1 *= i+1;
                fact2 *= i+2;
                fact3 *= i+3;
        }
        return fact0 * fact1 *fact2 * fact3;
}
int main()
{
        printf("%d\n", test(1000000000));
}

注意:这里为方便讲解,假设n总是4的倍数。如果要处理n不是4的倍数的情况,只需要在主循环体外,增加一个小的循环单独处理最后的n%4个数,也就是最多3个数即可,对整体性能影响几乎为0.

编译一下,然后用time命令测量下运行耗时:

[root@localhost ~]# time ./jiecheng2
0

real    0m9.067s
user    0m9.066s
sys     0m0.004s

运行耗时从原来的25秒降到了9秒,性能提升了!你以为这就完了?

这还不是最终的结果,因为我们还有一个优化技巧还没加上,最终优化后的结果是0.3秒!文末会讲!先不着急,咱们一个一个来讲!

关于循环展开:你真的理解吗?

看到这里,有人会说,不就是循环展开嘛,很简单的,没什么好研究的,而且加了优化选项之后,编译器会自动进行循环展开的,没必要手动去展开,也就没有研究的价值了!

真的是这样吗?先尝试回答下面几个问题:

  1. 循环展开为什么能提高程序性能,其背后的深层次原理是什么?

  2.  循环随便怎么展开都一定可以提高性能吗?

  3. 用了优化选项,编译器一定会帮我们自动进行循环展开优化吗?

第一个问题后面会详细讲解,我们先用实例回答下第2个和第3个问题。

先看第2个问题。

循环随便展开都能提高性能吗?

我们把jiecheng_2.c稍微改一下,命名为jiecheng_3.c:

#include <stdio.h>
int test(int n)
{
        int fact=0;
        int i =1;
        for (i = 1; i < n; i+=4) {
                fact *= i;
                fact *= i+1;
                fact *= i+2;
                fact *= i+3;
        }
        return fact;
}
int main()
{
        printf("%d\n", test(1000000000));
}

仍然是循环展开,只不过把循环展开的方式稍微改了一下。再编译一下,用time命令测量下运行耗时:

[root@localhost ~]# time ./jiecheng3
0

real    0m17.095s
user    0m17.090s
sys     0m0.004s

和jiecheng.c相比运行耗时只减少了8秒!为什么同样是循环展开,jiecheng_2.c只需要1.6秒,而jiehceng_3.c却要17秒,为什么性能差异这么大呢?别着急,后面细讲。

再看第三个问题,加了优化选项,编译器一定会帮我们自动进行循环展开优化吗?一试便知

O3,编译器一定会循环展开吗?

重新编译下test.c, test_2.c, 和test_3.c,只不过,这次我们加上-O3优化选项,然后分别用time命令再测量下运行时间。

先是test.c:

[root@localhost ~]# gcc jiecheng.c -o jiecheng -O3
[root@localhost ~]# time ./jiecheng
0

real    0m3.457s
user    0m3.453s
sys     0m0.004s

加了-O3优化后,程序耗时从原来的25秒降到了3秒,性能提升确实非常明显!是否好奇,-O3选项对test.c做了什么样的优化,能够把程序耗时降到八分之一?这个后面再讲。

现在,我们先试下test_2.c:

[root@localhost ~]# gcc jiecheng_2.c -o jiecheng2 -O3
[root@localhost ~]# time ./jiecheng2
0

real    0m1.011s
user    0m1.008s
sys     0m0.004s

同样,加了-O3后,程序耗时从原来的9秒降到了1秒!此外,在同样加了-O3的情况下,使用了循环展开的test_2.c,程序耗时仍然是test.c的八分之一可见,编译器确实优化了一些东西,但是,无论是否加-O3优化选项,进行手动循环展开的test.c仍然是性能最好的!

最后,再试下test_3.c:

[root@localhost ~]# gcc jiecheng_3.c -o jiecheng3 -O3
[root@localhost ~]# time ./jiecheng3
0

real    0m0.004s
user    0m0.000s
sys     0m0.004s

看到了吧?同样加了-O3优化选项的前提下,性能仍然与test_2.c相差甚远!(我这里测试的结果与文章的不一样),也是与CPU有关,这里使用的是龙芯Loongson-3A R3 (Loongson-3B3000) @ 1450MHz

小结一下我们现在得到的几组测试结果:

        jiecheng.c                jiecheng_2.c        jiecheng_3.c

-O0        25.159秒            9.075秒                  17.054秒      

-O3        3.463秒               1.008秒                 0.0003秒

原谅链接

改几行代码,for循环耗时从3.2秒降到0.3秒!真正看懂的都是牛人!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 这是一个计算阶乘和的算法问题。以下是我的回答: 可以使用循环来计算1!到n!的和。首先,定义一个变量sum来存储阶乘和的结果,然后使用一个for循环来计算每个数的阶乘并将其加到sum中。具体实现如下: int n = 10; // 输入的n int sum = ; // 阶乘和的结果 int factorial = 1; // 阶乘的计算结果 for (int i = 1; i <= n; i++) { factorial *= i; // 计算i的阶乘 sum += factorial; // 将阶乘加到sum中 } 这个算法可以正确地计算从1的阶乘到10的阶乘的和。如果n比较大,比如100,这个算法也是正确的,只是需要更多的时间和内存来计算。 ### 回答2: 算法如下: 1. 初始化阶乘和sum为0。 2. 对于输入的n,从i=1开始,依次计算1~n的阶乘: - 如果i=1,则将i的阶乘1加到sum中; - 否则,计算i的阶乘,并将结果加到sum中。 3. 返回计算结果sum。 这个算法的正确性是保证的,因为它根据输入的n依次计算了从1的阶乘到n的阶乘,并将它们的和保存在sum中。无论n的值多大,程序都会正确计算阶乘和。 不过,对于较大的n,该算法的时间复杂度较高,因为需要依次计算1~n的阶乘。因此,在计算阶乘和时,当n较大时,算法的执行时间会显著增加,效率比较低。可以考虑优化算法,如使用递归和记忆化技术,以减少计算量和提高执行效率。 ### 回答3: 这个算法的基本思路是使用一个循环来计算从1到n的阶乘,并将每次计算的结果累加起来。 具体实现如下: 1. 设定一个变量sum并初始化为0,用于存储阶乘的和。 2. 从1到n循环遍历,对于每个数i,计算其阶乘i!。 3. 在循环中,设定一个变量factorial并初始化为1,用于存储当前数的阶乘结果。 4. 内层循环计算i的阶乘,从1到i循环遍历,对于每个数j,更新factorial为factorial*j。 5. 将当前计算得到的阶乘factorial加到sum上,即sum = sum + factorial。 6. 循环结束后,输出sum作为结果。 这个算法在计算任意范围内的阶乘和时是正确的,无论n的值有多大。因为我们使用了循环结构,每次只计算一个阶乘,并将结果累加起来,不会有溢出的问题。无论是计算从1的阶乘到10的阶乘的和,还是从1的阶乘到100的阶乘的和,都可以通过这个算法得到正确的结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值