PL / SQL或替代方案

时间:2016-10-22 15:46:32

标签: oracle performance optimization plsql numerical

我们需要做一些计算繁重的工作来连接Oracle数据库。到目前为止,我们已经在PL / SQL中进行了数值计算,并且很大程度上缺乏性能。

现在我们正在实施多级库存模型(https://www.researchgate.net/publication/222409130_Evaluation_of_time-varying_availability_in_multi-echelon_spare_parts_systems_with_passivation)由于问题的严重性,我更关注性能。

我用三种语言实现了部分算法:Fortran(90-2008符合gfortran),Excel中的VBA和PL / SQL,并围绕它进行了一百万次调用测试循环。即使使用binary_double数据类型和使用PLSQL_CODE_TYPE=NATIVE进行本机编译(这两者都会带来改进),下面的测试代码仍然需要37秒才能运行(Oracle XE 11.2)。相比之下,VBA在同一硬件上占用16秒和Fortran 1.6。

虽然要求性能接近Fortran数据可能太过分了(虽然这显然是非常可取的)但我很惊讶即使是简陋的VBA也会执行PL / SQL。

所以我的问题有两部分:

  1. 我还可以尝试在Oracle方面进一步提高性能吗?
  2. 如果我们考虑摆脱PL / SQL,我们应该考虑哪些替代方案来连接Oracle数据库?虽然接近Fortran速度的性能提升将使我们考虑在应用程序的交互式部分中引入一些繁重的工作,但繁重的工作将以类似批处理的方式被称为相对较少。我首选的数值工作语言仍然是Fortran,因为它易于实现具有屏蔽功能的多维数组。
  3. 此外,虽然我不是直接批评我的源代码本身,但如果有人能发现任何我可以合并的明显优化,我将不胜感激。

    函数timeebo是测试函数,我在SQL Developer中使用简单的select timeebo from dual;调用它。

    create or replace FUNCTION gammln(
        x IN binary_double)
      RETURN binary_double
    IS
      --Lanczos' approximation to the Log Gamma function
      gammln binary_double;
      ser binary_double;
      tmp binary_double;
    BEGIN
      tmp := x + 5.5;
      tmp :=(x + 0.5) * Ln(tmp) - tmp;
      ser := 1.000000000190015;
      ser := ser + 76.18009172947146 /(x + 1.0) ;
      ser := ser - 86.50532032941677 /(x + 2.0) ;
      ser := ser + 24.01409824083091 /(x + 3.0) ;
      ser := ser - 1.231739572450155 /(x + 4.0) ;
      ser := ser + 1.208650973866179E-03 /(x + 5.0) ;
      ser := ser - 5.395239384953E-06 /(x + 6.0) ;
      RETURN tmp + Ln(2.5066282746310005 * ser / x) ;
    END;
    /
    CREATE OR REPLACE FUNCTION PoissonDist(
        k      IN INTEGER,
        lambda IN binary_double)
      RETURN binary_double
    IS
    BEGIN
      RETURN Exp((k * Ln(lambda)) - lambda - gammln(k + 1)) ;
    END;
    /
    CREATE OR REPLACE FUNCTION EBO(
        stock    IN pls_integer,
        pipeline IN binary_double,
        accuracy IN binary_double DEFAULT 0.0000000001)
      RETURN binary_double
    IS
      i pls_integer;
      EBO binary_double;
      term binary_double;
      temp binary_double;
      PoissonVal binary_double;
      peaked BOOLEAN; --Flag the Poisson curve as having peaked
    BEGIN
      EBO        := 0.0;
      IF(pipeline = 0.0) THEN
        RETURN EBO;
      END IF;
    --Initialise
    i            := 1;
    peaked       := false;
    PoissonVal   := PoissonDist(stock + 1, pipeline) ;         --Get p() value
    IF(PoissonVal < accuracy AND floor(pipeline) > stock) THEN --If p() is very
      -- small...
      i          := floor(pipeline)   - stock;         --Revise i to just below peak of Poisson curve
      PoissonVal := PoissonDist(stock + i, pipeline) ; --Get p() value close to
      -- peak
      temp := PoissonVal *(pipeline / CAST(stock + i + 1 AS binary_double)) ; --
      -- Store poisson value just above peak
      LOOP
        term       := CAST(i AS binary_double) * PoissonVal;
        EBO        := EBO                         + term;
        i          := i                           - 1; --Work backwards
        PoissonVal := PoissonVal                  *(CAST(stock + i + 1 AS DOUBLE
        PRECISION)                                / pipeline) ; --Revise Poisson
        -- value for next time
        EXIT
      WHEN(term < accuracy OR i = 0) ;
      END LOOP;
      i          := 1 + floor(pipeline) - stock;
      PoissonVal := temp;
      peaked     := true;
    END IF;
    LOOP
      term       := CAST(i AS binary_double) * PoissonVal;
      EBO        := EBO                         + term;
      i          := i                           + 1;
      PoissonVal := PoissonVal                  *(pipeline / CAST(stock + i AS
      binary_double)) ; --Revise Poisson value for next time
      IF(CAST(stock + i AS binary_double) > pipeline) THEN
        peaked                              := true;
      END IF;
      EXIT
    WHEN(term < accuracy AND peaked) ;
    END LOOP;
    IF(EBO < accuracy) THEN
      EBO := 0.0;
    END IF;
    RETURN EBO;
    END;
    /
    CREATE OR REPLACE FUNCTION timeebo
      RETURN binary_double
    IS
      i pls_integer;
      EBOVal binary_double;
      acc binary_double;
    BEGIN
      acc := 0.0;
      FOR i IN 1..1000000
      LOOP
        EBOVal := EBO(500, CAST(i AS binary_double) / 1000.0) ;
        acc    := acc                                + EBOVal;
      END LOOP;
    RETURN acc;
    END;
    

2 个答案:

答案 0 :(得分:3)

这不是OP问题的答案(但这是一个&#34;概念验证&#34;展示他在PL /他所做的事情之类的事情SQL可以在普通的SQL中完成。我只使用答案格式,因为我将在下面做的不适合评论。

OP要求了解如何在纯SQL中完成无限级数的求和,达到所需的精度。我将举两个例子。

第一个例子

具有正面术语的无限系列: e = sum [ j = 0 infinity ]( 1 / factorial j 。从0到n之和的误差的平凡上限是添加到系列的最后一个项。下面的递归查询(需要Oracle 11.1或更高版本 - 实际上我编写它的方式是11.2,在声明中使用列名,但是可以很容易地为11.1更改)计算 e 的值精确到小数点后38位(Oracle中可用的最大精度)。 e 的逆因子序列收敛得非常快;这只需要35步,并且在我的旧家用电脑(这只是带键盘的戴尔平板电脑)上运行时间不到0.001秒。

修改:嗯!只有我可以发布 e = 3.71828的内容!即使在递归查询中我添加了所有条件(包括1/0!),我从1开始求和而不是0.(现在更正,但在纠正之前有这个错误。)

with 
     rec ( j, s, next_term, err ) as (
       select  0, 0, 1, 2
         from  dual
       union all
       select  j+1, s + next_term, next_term/(j+1), next_term
         from  rec
         where err > power(10, -38) and j < 1000000000
     )
select max(j) as steps, round(max(s), 38) as e 
from   rec
;

STEPS                                         E
-----  ----------------------------------------
   35  2.71828182845904523536028747135266249776

第二个例子

好的,现在让我们采取一个交替的序列(其中最后一个词的绝对值总是误差的上限),让我们采取一个非常缓慢的收敛:< / p>

ln( 2 = 总和[ j = 1 < em>无穷大 ](( - 1)^( j - 1)/ j

下面的查询计算ln(2),精确到小数点后五位;在这里我们事先知道我们需要100,000步,而我的机器上的计算大约需要1.1秒。 (请记住,这是一个非常缓慢融合的系列。)

with
     rec ( j, s, sgn ) as (
       select  0, 0, 1
         from  dual
       union all
       select  j+1, s + sgn / (j+1), -sgn
         from  rec
         where j <= 100000
     )
select 100000 as steps, round(s, 5) as ln_2
from   rec
where  j = 100000
;

 STEPS     LN_2
------  -------
100000  0.69314

答案 1 :(得分:1)

根据上述评论中的建议总结我的发现:

消除铸造对建议的改进产生了最大的影响。在binary_double中执行所有操作可使代码运行速度提高3.5倍,超过10秒。因此,应用simple_doublepragma_inline会将此平均减少到大约9.8秒。

我的代码的当前版本,应用了上述更改,如下所示。 PoissonDist函数可以进一步优化,但最初的问题更多的是关于如何使现有代码运行得更快,而不是代码本身的优化。

编辑:代码已经过修改,以反映pragma_inline的正确使用,这将执行时间缩短到9.5秒左右。

create or replace FUNCTION gammln(
    x IN simple_double)
  RETURN simple_double
IS
  --Lanczos' approximation to the Log Gamma function
  ser simple_double := 1.000000000190015d;
  tmp simple_double := x + 5.5d;
BEGIN
  tmp :=(x + 0.5d) * Ln(tmp) - tmp;
  ser := ser + 76.18009172947146d /(x + 1.0d) ;
  ser := ser - 86.50532032941677d /(x + 2.0d) ;
  ser := ser + 24.01409824083091d /(x + 3.0d) ;
  ser := ser - 1.231739572450155d /(x + 4.0d) ;
  ser := ser + 1.208650973866179E-03d /(x + 5.0d) ;
  ser := ser - 5.395239384953E-06d /(x + 6.0d) ;
  RETURN tmp + Ln(2.5066282746310005d * ser / x) ;
END;
/
create or replace FUNCTION PoissonDist(
    k      IN simple_double,
    lambda IN simple_double)
  RETURN simple_double
IS
BEGIN
  PRAGMA INLINE (gammln, 'YES');
  RETURN Exp((k * Ln(lambda)) - lambda - gammln(k + 1d)) ;
END;
/
CREATE OR REPLACE FUNCTION EBO(
    stock    IN simple_double,
    pipeline IN simple_double,
    accuracy IN simple_double DEFAULT 0.0000000001)
  RETURN simple_double
IS
  i simple_double          := 1d;
  EBO simple_double        := 0d;
  term simple_double       := 0d;
  temp simple_double       := 0d;
  PRAGMA INLINE(PoissonDist, 'YES') ;
  PoissonVal simple_double := PoissonDist(stock + 1d, pipeline) ;
  peaked BOOLEAN           := false; --Flag the Poisson curve as having peaked
BEGIN
  IF(pipeline = 0.0d) THEN
    RETURN EBO;
  END IF;
IF(PoissonVal < accuracy AND floor(pipeline) > stock) THEN --If p() is very
  -- small...
  i          := floor(pipeline)   - stock;         --Revise i to just below peak of Poisson curve
  PRAGMA INLINE(PoissonDist, 'YES') ;
  PoissonVal := PoissonDist(stock + i, pipeline) ; --Get p() value close to
  -- peak
  temp := PoissonVal *(pipeline /(stock + i + 1d)) ; --
  -- Store poisson value just above peak
  LOOP
    term       := i          * PoissonVal;
    EBO        := EBO        + term;
    i          := i          - 1d;                           --Work backwards
    PoissonVal := PoissonVal *((stock + i + 1) / pipeline) ; --Revise Poisson
    -- value for next time
    EXIT
  WHEN(term < accuracy OR i = 0d) ;
  END LOOP;
  i          := 1d + floor(pipeline) - stock;
  PoissonVal := temp;
  peaked     := true;
END IF;
LOOP
  term       := i          * PoissonVal;
  EBO        := EBO        + term;
  i          := i          + 1d;
  PoissonVal := PoissonVal *(pipeline /(stock + i)) ; --Revise Poisson value
  -- for next time
  IF((stock + i) > pipeline) THEN
    peaked      := true;
  END IF;
  EXIT
WHEN(term < accuracy AND peaked) ;
END LOOP;
IF(EBO < accuracy) THEN
  EBO := 0.0d;
END IF;
RETURN EBO;
END;
/
create or replace FUNCTION timeebo
  RETURN binary_double
IS
  i binary_double;
  EBOVal binary_double;
  acc binary_double;
BEGIN
  acc := 0.0d;
  FOR i IN 1d..1000000d
  LOOP
    PRAGMA INLINE (EBO, 'YES');
    EBOVal := EBO(500d, i / 1000d) ;
    acc    := acc                                + EBOVal;
  END LOOP;
RETURN acc;
END;