看到有人实现了一个计算质数的函数,就是效率有点差,贴一个以前写的计算质数的算法。

目标很简单,列出100以内的质数。
其实算法很简单,两个循环就搞定了。但是发现使用不同的算法,执行效率差别之大相当惊人,特别是数据量级很大的时候。

下面就是最常见的一种写法:(也是最差的一种)

[PHP]

SQL> SET SERVEROUT ON
SQL> DECLARE
  2   V_FLAG BOOLEAN;
  3  BEGIN
  4   FOR I IN 2 .. 100 LOOP
  5    V_FLAG := TRUE;
  6    FOR J IN 2 .. I - 1 LOOP
  7     IF  MOD(I,J) = 0 THEN
  8      V_FLAG := FALSE;
  9     END IF;
10    END LOOP;
11  
12    IF V_FLAG = TRUE THEN
13     DBMS_OUTPUT.PUT_LINE(I);
14    END IF;
15   END LOOP;
16  END;
17  /
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
PL/SQL 过程已成功完成。
已用时间:  00: 00: 00.09

.
[/PHP]

由于屏幕输出操作比较慢,为了避免影响,将屏幕输出关闭。并将数据量增大到100000,以下所有的测试都在这个相同条件下进行。

[PHP]

SQL> DECLARE
  2   V_FLAG BOOLEAN;
  3  BEGIN
  4   FOR I IN 2 .. 100000 LOOP
  5    V_FLAG := TRUE;
  6    FOR J IN 2 .. I - 1 LOOP
  7     IF  MOD(I,J) = 0 THEN
  8      V_FLAG := FALSE;
  9     END IF;
10    END LOOP;
11  
12    IF V_FLAG = TRUE THEN
13     --DBMS_OUTPUT.PUT_LINE(I);
14     NULL;
15    END IF;
16   END LOOP;
17  END;
18  /
PL/SQL 过程已成功完成。
已用时间:  02: 02: 58.73

.
[/PHP]

这种方法在100000数据量的用时居然达到了2个小时。
如果稍微仔细考虑一下,就会发现,系统做了很多没有必要的工作,首先判断是否能整除的时候不需要循环到I – 1,只要执行到I的平方根就可以了,而且,如果I可以被整除就不需要继续循环,可以马上跳出内层循环了。经过简单优化后:

[PHP]

SQL> DECLARE
  2   V_FLAG BOOLEAN;
  3  BEGIN
  4   FOR I IN 2 .. 100000 LOOP
  5    V_FLAG := TRUE;
  6    FOR J IN 2 .. TRUNC(POWER(I, 0.5)) LOOP
  7     IF  MOD(I,J) = 0 THEN
  8      V_FLAG := FALSE;
  9      EXIT;
10     END IF;
11    END LOOP;
12  
13    IF V_FLAG = TRUE THEN
14     --DBMS_OUTPUT.PUT_LINE(I);
15     NULL;
16    END IF;
17   END LOOP;
18  END;
19  /
PL/SQL 过程已成功完成。
已用时间:  00: 00: 16.21

.
[/PHP]

效果十分的明显,从2个多小时,缩减到了16秒。
算法还可以进一步优化,考虑到质数中只有2是偶数,其他都是奇数,可以将2单独处理,然后将循环的步长设置为2,这样外层循环次数就减少了一半。

[PHP]

SQL> DECLARE
  2   I NUMBER DEFAULT 3;
  3   V_FLAG BOOLEAN;
  4  BEGIN
  5   --DBMS_OUTPUT.PUT_LINE(2);
  6   WHILE I < 100000 LOOP
  7    V_FLAG := TRUE;
  8    FOR J IN 2 .. TRUNC(POWER(I, 0.5)) LOOP
  9     IF  MOD(I,J) = 0 THEN
10      V_FLAG := FALSE;
11      EXIT;
12     END IF;
13    END LOOP;
14  
15    IF V_FLAG = TRUE THEN
16     --DBMS_OUTPUT.PUT_LINE(I);
17     NULL;
18    END IF;
19   I := I + 2;
20   END LOOP;
21  END;
22  /
PL/SQL 过程已成功完成。
已用时间:  00: 00: 09.37

.
[/PHP]

仔细考虑一下,其实用来被整除的数是质数就足够了,不需要对所有奇数进行判断。在下面的过程中,使用索引表来保存计算得到的所有的质数:

[PHP]

SQL> DECLARE
  2   TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  3   V_RESULT T_RECORD;
  4   V_FLAG BOOLEAN;
  5   V_CNT NUMBER;
  6   I NUMBER DEFAULT 3;
  7  BEGIN
  8   V_RESULT(1) := 2;
  9   --DBMS_OUTPUT.PUT_LINE(V_RESULT(1));
10   WHILE(I < 100000) LOOP
11    V_FLAG := TRUE;
12    V_CNT := V_RESULT.COUNT;
13    FOR J IN 1..V_CNT LOOP
14     IF V_RESULT(J) > POWER(I, 0.5) THEN
15      EXIT;
16     END IF;
17     IF MOD(I,V_RESULT(J)) = 0 THEN
18      V_FLAG := FALSE;
19      EXIT;
20     END IF;
21    END LOOP;
22    IF V_FLAG THEN
23    -- DBMS_OUTPUT.PUT_LINE(I);
24     V_RESULT(V_CNT+1) := I;
25    END IF;
26    I := I + 2;
27   END LOOP;
28  END;
29  /
PL/SQL 过程已成功完成。
已用时间:  00: 00: 06.68

.
[/PHP]

已经将速度提高到了6秒左右,还能不能更快呢?注意到在最内层循环中调用了一个函数POWER(I, 0.5),下面将这个表达式转换一下,避免使用这个函数:

[PHP]

SQL> DECLARE
  2   TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  3   V_RESULT T_RECORD;
  4   V_FLAG BOOLEAN;
  5   I NUMBER DEFAULT 3;
  6  BEGIN
  7   --DBMS_OUTPUT.PUT_LINE(2);
  8   V_RESULT(1) := 2;
  9   WHILE(I < 100000) LOOP
10    V_FLAG := TRUE;
11    FOR J IN 1..V_RESULT.COUNT LOOP
12     IF V_RESULT(J) * V_RESULT(J) > I THEN
13      EXIT;
14     END IF;
15     IF MOD(I,V_RESULT(J)) = 0 THEN
16      V_FLAG := FALSE;
17      EXIT;
18     END IF;
19    END LOOP;
20    IF V_FLAG THEN
21    -- DBMS_OUTPUT.PUT_LINE(I);
22     V_RESULT(V_RESULT.COUNT + 1) := I;
23    END IF;
24    I := I + 2;
25   END LOOP;
26  END;
27  /
PL/SQL 过程已成功完成。
已用时间:  00: 00: 01.03

.
[/PHP]

难以置信吧,一个执行两个小时的PL/SQL,通过算法的调整可以优化到了1秒。
其实能优化的地方还有很多,不过这些优化能带来的性能提升已经很小了。比如:

[PHP]

SQL> DECLARE
  2   TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  3   V_RESULT T_RECORD;
  4   V_FLAG BOOLEAN;
  5   I NUMBER DEFAULT 3;
  6  BEGIN
  7   --DBMS_OUTPUT.PUT_LINE(2);
  8   V_RESULT(0) := 2;
  9   WHILE(I < 100000) LOOP
10    V_FLAG := TRUE;
11    FOR J IN 1..V_RESULT.COUNT - 1 LOOP
12     IF V_RESULT(J) * V_RESULT(J) > I THEN
13      EXIT;
14     END IF;
15     IF MOD(I,V_RESULT(J)) = 0 THEN
16      V_FLAG := FALSE;
17      EXIT;
18     END IF;
19    END LOOP;
20    IF V_FLAG THEN
21    -- DBMS_OUTPUT.PUT_LINE(I);
22     V_RESULT(V_RESULT.COUNT) := I;
23    END IF;
24    I := I + 2;
25   END LOOP;
26  END;
27  /
PL/SQL 过程已成功完成。
已用时间:  00: 00: 00.96

.
[/PHP]

由于从3开始步长为2,因此判断随后的质数的时候,没有必要用2去整除,而直接可以从3开始。
过程仍然可以进一步优化,可以省略掉不必要的赋值和判断语句:

[PHP]

SQL> DECLARE
  2   TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  3   V_RESULT T_RECORD;
  4   I NUMBER DEFAULT 3;
  5  BEGIN
  6   --DBMS_OUTPUT.PUT_LINE(2);
  7   V_RESULT(1) := 3;
  8   WHILE(I < 100000) LOOP
  9    FOR J IN 1..V_RESULT.COUNT LOOP
10     IF MOD(I,V_RESULT(J)) = 0 THEN
11      EXIT;
12     END IF;
13     IF V_RESULT(J) * V_RESULT(J) > I THEN
14     -- DBMS_OUTPUT.PUT_LINE(I);
15      V_RESULT(V_RESULT.COUNT + 1) := I;
16      EXIT;
17     END IF;
18    END LOOP;
19    I := I + 2;
20   END LOOP;
21   V_RESULT(0) := 2;
22  END;
23  /
PL/SQL 过程已成功完成。
已用时间:  00: 00: 00.96

.
[/PHP]
但是在100000这个数量级已经看不出性能的差别了。正如Tom所说的,系统总是可以提高1%的性能,不过付出的代价会越来越大。
刚才测试发现,将MOD函数转换一下,性能还会有一个相对明显的提升

[PHP]

SQL> DECLARE
  2   TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  3   V_RESULT T_RECORD;
  4   I NUMBER DEFAULT 3;
  5  BEGIN
  6   --DBMS_OUTPUT.PUT_LINE(2);
  7   V_RESULT(1) := 3;
  8   WHILE(I < 100000) LOOP
  9    FOR J IN 1..V_RESULT.COUNT LOOP
10     IF V_RESULT(J) * V_RESULT(J) > I THEN
11      --DBMS_OUTPUT.PUT_LINE(I);
12      V_RESULT(V_RESULT.COUNT + 1) := I;
13      EXIT;
14     END IF;
15     IF TRUNC(I/V_RESULT(J)) = I/V_RESULT(J) THEN
16      EXIT;
17     END IF;
18    END LOOP;
19    I := I + 2;
20   END LOOP;
21   V_RESULT(0) := 2;
22  END;
23  /
PL/SQL 过程已成功完成。
已用时间:  00: 00: 00.87

.
[/PHP]

再来一次尝试:

[PHP]

SQL> DECLARE
  2   TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  3   V_RESULT T_RECORD;
  4   I NUMBER DEFAULT 3;
  5   N NUMBER DEFAULT 0;
  6  BEGIN
  7   --DBMS_OUTPUT.PUT_LINE(2);
  8   V_RESULT(1) := 3;
  9   WHILE(I < 100000) LOOP
10    FOR J IN 1..V_RESULT.COUNT LOOP
11     IF V_RESULT(J) * V_RESULT(J) > I THEN
12      --DBMS_OUTPUT.PUT_LINE(I);
13      V_RESULT(V_RESULT.COUNT + 1) := I;
14      EXIT;
15     END IF;
16     IF TRUNC(I/V_RESULT(J)) = I/V_RESULT(J) THEN
17      EXIT;
18     END IF;
19    END LOOP;
20    IF N = 2 THEN
21     I := I + 4;
22     N := 1;
23    ELSE
24     I := I + 2;
25     N := N + 1;
26    END IF;
27   END LOOP;
28   V_RESULT(0) := 2;
29  END;
30  /
PL/SQL 过程已成功完成。
已用时间:  00: 00: 00.84

.
[/PHP]

看来Tom说的确实没有错,优化的方法总是存在的。

原文出自:http://space.itpub.net/4227/viewspace-68767

PLSQL计算质数的更多相关文章

  1. Eratosthenes筛选法计算质数

    <C和指针>第6章第4道编程题: 质数就是只能被1和本身整除的数.Eratosthenes筛选法是一种计算质数的有效方法.这个算法的第一步就是写下所有从2至某个上限之间的所有整数.在算法的 ...

  2. oracle /plsql 计算平闰年天数函数

    --计算平闰年天数函数 CREATE OR REPLACE FUNCTION f_ping_run_nian (i_year NUMBER --定义函数名 ) RETURN VARCHAR2 IS - ...

  3. .Net基础篇_学习笔记_第七天_计算质数(找出0-100以内说有质数)

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  4. 欧拉降幂,基本计算定理——cf615D

    用基本算数定理求约数和的思想来计算, 首先用pi,ci来表示第i个质数,指数为i,然后对于每个pi,pi^2...都有指数为mul{(c_1+1)(c_2+1)(c_i-1+1)(c_i+1+1).. ...

  5. Android——Handler

    Handler——是Android给我们提供用来更新UI的一套机制,也是一套消息处理机制,可以发送也可以处理消息 主要作用:1)在新启动的线程中发送消息:2)在主线程中获取.处理消息. (想想银行取钱 ...

  6. 【BZOJ-2440】完全平方数 容斥原理 + 线性筛莫比乌斯反演函数 + 二分判定

    2440: [中山市选2011]完全平方数 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 2371  Solved: 1143[Submit][Sta ...

  7. 《C与指针》第六章练习

    本章问题 1.如果一个值的类型无法简单的通过观察它的位模式来判断,那么机器是如何知道应该怎样对这个值进行操纵的? answer:The machine doesn't make this determ ...

  8. 使用sysbench进行压力测试

    sysbench是一款开源的多线程性能测试工具,可以执行CPU/内存/线程/IO/数据库等方面的性能测试.数据库目前支持MySQL/Oracle/PostgreSQL/Drizzle,它主要包括以下几 ...

  9. [转载]:Delphi xe7并行编程快速入门

    现在多数设备.计算机都有多个CPU单元,即使是手机也是多核的.但要在开发中使用多核的优势,却需要一些技巧,花费时间编写额外的代码.好了,现在可以使用Delphi做并行编程了. 在Delphi.C++ ...

随机推荐

  1. 疑难杂症--SQL SERVER 2012下数据库内存异常回收

    --=================================================================== --背景: 在一台SQL SERVER 2012 SP1(1 ...

  2. AbpZero后台模块化(1)

    AbpZero的精髓就在于多租户模块化加载,如果不做到这两种的话,就没必要使用这个框架. 1.首先,我们得新建一个类库,用于存放我们写的业务代码.       在类库下分别建立三个类文件:TestAp ...

  3. C#克隆

    克隆方法是原型设计模式中必须使用的方式,它将返回一个与当前对象数据一致的对象.正如其名,犹如一个模子雕刻而出.克隆类型分为两种:浅克隆.深克隆. 1.浅克隆 浅克隆方式是最简单.最直接的方式.只需要类 ...

  4. 创建/删除Cookie数据

    //1.编写(创建 和 修改 一样) HttpCookie cookie = new HttpCookie("userName");cookie.Value = "顾志海 ...

  5. 企业项目开发--分布式缓存memcached(3)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3.3.ssmm0-data 结构: 3.3.1.pom.xml  1 <?xml version=& ...

  6. nginx 场景业务汇总 (中)

    本文链接:http://www.cnblogs.com/zhenghongxin/p/8906225.html,如果可以,请阅读上篇 <nginx场景业务汇总(初)> (十三)负载均衡 轮 ...

  7. Exp4 恶意代码分析 20164321 王君陶

    Exp4 恶意代码分析 20164321 王君陶 1.实践目标 1.1是监控你自己系统的运行状态,看有没有可疑的程序在运行. 1.2是分析一个恶意软件,就分析Exp2或Exp3中生成后门软件:分析工具 ...

  8. Atcoder Tenka1 Programmer Contest 2019题解

    传送门 \(C\ Stones\) 最后肯定形如左边一段白+右边一段黑,枚举一下中间的断点,预处理一下前缀和就可以了 int main(){ // freopen("testdata.in& ...

  9. 读取GY-951模块数据(Linux)

    参考:https://github.com/Razor-AHRS/razor-9dof-ahrs/wiki/Tutorial#sensor-calibration 链接中使用的硬件是SparkFun“ ...

  10. 关于finecms v5 会员头像 任意文件上传漏洞分析

    看到我私藏的一个洞被别人提交到补天拿奖金,所以我干脆在社区这里分享,给大家学习下 本文原创作者:常威,本文属i春秋原创奖励计划,未经许可禁止转载! 1.定位功能 下载源码在本地搭建起来后,正常登陆了用 ...