We have a requirement to do some computational heavy lifting to connect with an Oracle database. Up until now, we have performed our numerical calculations in PL/SQL and largely put up with the lack of performance.
Now we are implementing a multi-echelon inventory model (https://www.researchgate.net/publication/222409130_Evaluation_of_time-varying_availability_in_multi-echelon_spare_parts_systems_with_passivation) I am much more concerned about the performance due to the size of the problem.
I have implemented part of the algorithm in three languages: Fortran (90-2008 complied with gfortran), VBA in Excel and PL/SQL, and wrapped a one-million-calls test loop around it. Even using binary_double
data type and native compilation using PLSQL_CODE_TYPE=NATIVE
(both of which result in an improvement) the test code below still takes 37s to run (Oracle XE 11.2). In comparison, VBA takes 16s and Fortran 1.6s on the same hardware.
While it would probably be too much to ask for performance approaching the Fortran figure (though this would obviously be highly desirable) I am surprised that even humble VBA out performs the PL/SQL.
So my question has two parts:
- Is there anything else I can try on the Oracle side to improve the performance further?
- If we consider moving away from PL/SQL, what alternatives should we consider for interfacing to the Oracle database? The heavy lifting will be called relatively seldom in a batch mode-like fashion, although a performance improvement to that approaching the speed of Fortran would allow us to consider introducing some of the heavy lifting in the the interactive parts of the application. My preferred language for numerical work remains Fortran for its ease of implementation of multi-dimensional arrays with masking.
Also, though I am not directly after a critique of my source code per se, I would be grateful if anyone can spot any obvious optimizations which I can incorporate.
The function timeebo
is the test function, which I am invoking with a simple select timeebo from dual;
in 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;