我们需要做一些计算繁重的工作来连接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。
所以我的问题有两部分:
此外,虽然我不是直接批评我的源代码本身,但如果有人能发现任何我可以合并的明显优化,我将不胜感激。
函数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;
答案 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_double
和pragma_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;