zoukankan      html  css  js  c++  java
  • PLSQL计算质数

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

    目标很简单,列出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
  • 相关阅读:
    十九:数字排重
    十八:十六进制转八进制
    Dubbo Notes
    Concurrent Package
    MySQL(8.0)官方文档阅读笔记
    MyBatis笔记
    分布式事务
    RabbitMQ笔记
    Kafka官方文档阅读笔记
    Cassandra Note
  • 原文地址:https://www.cnblogs.com/peijie-tech/p/4878184.html
Copyright © 2011-2022 走看看