目前我们有相当多的函数(正态 CDF、逆 CDF、Vasicek 和各种导数)用 PL/SQL 编写,但它们非常慢。
I can get much better performance by streaming the data over a workstation where I have coded out things in C# and then bulk insert the results back. This approach however leaves the network as the bottleneck, it would be much better if I could 'put the mill where the wood is' by having faster functions inside the Oracle DB.
I want to see how I can speed that up by coding it out in either c(++) or Java (or any other alternative you may have). Does anyone here have any experience with this? Hopefully one of you has tried all approaches and can explain which has worked best overall.
Extra complication here is that IT is busy as it is, so if I want a waiver to use some feature on the DB I need to make a solid case. I don;t get to play around much on that box, else I would do that.
我们使用 Oracle Database 11g 企业版 11.2.0.2.0 - 64 位生产版
提前致谢,
格特-扬
编辑
下面是一个函数的示例,即Cody 的 Normal CDF。
它与 的区别cume_dist
在于cume_dist
查找一组行内的分布。我只需要将概率转换为标准差并转换回来(很多次),就像Excel 中的NORMDIST
和函数一样。NORMINV
function stdnormal_cdf(u number) return number is
z number;
y Number;
begin
y:=abs(u);
if y <= 0.6629126073623883041257915894732959743297 then
z:=y * y;
y:=u * ((((1.161110663653770e-002 * z + 3.951404679838207e-001) * z + 2.846603853776254e + 001) * z + 1.887426188426510e + 002) * z + 3.209377589138469e + 003)/((((1.767766952966369e-001 * z + 8.344316438579620) * z + 1.725514762600375e + 002) * z + 1.813893686502485e + 003) * z + .044716608901563e + 003);
return 0.5 + y ;
else
z:=exp(-y * y/2)/2;
if y <= 5.65685424949238019520675489683879231428 then
y:=y/1.41421356237309504880168872420969807857;
y:=((((((((2.15311535474403846e-8 * y + 5.64188496988670089e-1) * y + 8.88314979438837594) * y + 6.61191906371416295e01) * y + 2.98635138197400131e02) * y + 8.81952221241769090e02) * y + 1.71204761263407058e03) * y + 2.05107837782607147e03) * y + 1.23033935479799725e03)/((((((((1.00000000000000000e00 * y + 1.57449261107098347e01) * y + 1.17693950891312499e02) * y + 5.37181101862009858e02) * y + 1.62138957456669019e03) * y + 3.29079923573345963e03) * y + 4.36261909014324716e03) * y + 3.43936767414372164e03) * + 1.23033935480374942e03);
y:=z * y;
else
z:=z * 1.41421356237309504880168872420969807857/y;
y:=2/(y * y);
y:=y * (((((1.63153871373020978e-2 * y + 3.05326634961232344e-1) * y + 3.60344899949804439e-1) * y + 1.25781726111229246e-1) * y + 1.60837851487422766e-2) * y + 6.58749161529837803e-4)/(((((y + 2.56852019228982242) * y + 1.87295284992346047) * y + 5.27905102951428412e-1) * y + 6.05183413124413191e-2) * y + 2.33520497626869185e-3);
y:=z * (1/1.77245385102123321827450760252310431421-y);
end if;
if u < 0 then
return y;
else
return 1-y;
end if;
end if;
end;
编辑2
好的,这是基准。具有 100k 行的测试表。Oracle 和 F# 之间的函数是彼此非常直接的翻译,并给出相同的结果。
查询:
select
sum(get_rwa(approach, exposure_class_code, pd_r, lgd_r, ead_r, maturity_r, net_sale, rwf_r))
from functest
- 翻译:12.8 秒
- 本机:13.2 秒
- .Net (F#):0.04 秒。
This would make the .Net function 320x (!) faster than the Oracle implementation, I really don't understand where this difference could come from. Anything up to 3-10x would seem reasonable. I really think I'm missing something here. Anyone?
In F# I loaded the 100k rows into a List first. (seemed fair, just summing up any other column in Oracle cost 0.06 seconds, so it seemed fair to exclude the data access time in both cases. It takes about 3 sec to load the data into a list, so even if I include the time it takes to open up the connection, execute and stream over the networks etc, then still it's 4x faster.)
Oracle 支持定义和调用外部过程的能力。假设您可以将 C/C++/C# 应用程序编译为 DLL/.so 并将该库移动到数据库服务器,然后您可以将 DLL 的函数公开为外部过程,然后从数据库内调用 DLL 的函数。由于所有内容都将在同一台计算机上运行,因此网络不会成为瓶颈。当然,这意味着您的 C/C++/C# 代码将使用服务器的处理资源——这可能是好事,也可能不是好事,具体取决于服务器的 CPU 与工作站的 CPU 相比有多强大以及服务器的其他功能正在做。
根据您在 PL/SQL 中编码逻辑的具体方式,您可能还想研究利用 Oracle 的内置分析函数,例如用于累积分布的cume_dist(我假设这就是您所说的“正态 CDF”)或编写自己的分析函数。由于您的代码是计算密集型的,因此您也很有可能从本机编译中受益。当然,这假设您已经分析了代码,并且没有明显的地方/方法来调整 PL/SQL。
格特-扬,
时间差异可能是由于 SQL 引擎和 PL/SQL 引擎之间的上下文切换造成的。functest 中的 100,000 行中的每一行都通过 PL/SQL 例程 get_rwa(和/或 stdnormal_cdf)进行处理。上下文切换涉及保存状态和恢复状态,完成一次后您可能不会注意到。但做 100,000 次就会积少成多。
因此,我建议在包含 100,000 行的嵌套表中加载 100,000 行,并将该嵌套表仅传递给 PL/SQL 例程一次,该例程执行简单的“for i in 1 .. [nested_table_variable].count 循环 ..” .结束循环;”,同时总结各个结果。
另一种选择是在 SQL 中完成所有操作,而不求助于 PL/SQL。
问候,
罗布。