Mat*_*ham 6 oracle optimization performance numerical plsql
我们需要做一些计算繁重的工作来连接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在同一硬件上占用16s和Fortran 1.6s.
虽然要求性能接近Fortran数字可能太过分了(虽然这显然是非常需要的)但我很惊讶即使是不起眼的VBA也会执行PL/SQL.
所以我的问题有两个部分:
此外,虽然我不是直接批评我的源代码本身,但如果有人能发现任何我可以合并的明显优化,我将不胜感激.
该函数timeebo是测试函数,我select timeebo from dual;在SQL Developer中使用一个简单的函数调用它.
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;
Run Code Online (Sandbox Code Playgroud)
小智 3
这不是OP问题的答案(但它是一个“概念证明”,展示了如何在普通SQL中完成他在PL/SQL中所做的事情)。我只使用答案格式,因为我下面要做的事情不适合评论。
OP 要求了解如何在纯 SQL 中将无限级数求和到所需的精度。我举两个例子。
第一个例子:
具有正项的无穷级数:e = sum [ j = 0到 无穷大 ] ( 1 /阶乘( 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
Run Code Online (Sandbox Code Playgroud)
第二个例子:
好的,现在让我们采用一个交替级数(其中最后一项的绝对值始终是误差的上限),然后让我们采用一个非常慢收敛的级数:
ln( 2 ) =总和 [ j = 1到 无穷大 ] ( (-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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
288 次 |
| 最近记录: |