2

我的一个同事将一个 javascript 逆卡方例程转换为 Oracle。好消息是它返回与 javascript 例程相同的结果。坏消息是,在 IE 或 Chrome 中返回结果需要 1.5 秒,而在 Oracle 中需要 23 秒。在这 23 秒中 >99% 是 CPU 时间。

例程中有两个循环,一个为我们正在测试的值执行 36 次的外部循环,一个为外部循环的每次迭代运行 10,753 次的内部循环。它在两个 JS 中执行与在 Oracle 中相同的循环。对于内部循环的每次迭代,它都会执行一个 EXP 函数和一个 LN 函数,这两种语言都是固有的。

我已经编译了 Interpreted 和 Native 的 Oracle 代码,几乎没有任何变化(0.045 秒差异)。

我有三个问题;

  1. 为什么 Oracle 这么慢/我该如何改进它?
  2. Oracle中是否有内在的反卡方函数。
  3. 有没有人有一个反卡方函数,不需要像我使用的那样迭代循环(或不需要那么多)?

一个额外的问题是;

有没有人有一个计算 PL/SQL 中的置信区间的例程,或者一种可以轻松转换为 PL/SQL 的语言?

根据要求,这里是代码,有点长(主要程序是CRITCHI,用于测试P=0.975和DF=21507.38);

BIGX Number :=20;

FUNCTION POZ(Z IN NUMBER) RETURN NUMBER IS
  Y NUMBER;
  X NUMBER;
  W NUMBER;
  Z_MAX NUMBER;
  XXX NUMBER;
BEGIN  
      Z_MAX:=6.0;


      IF (Z=0) THEN 
          X:= 0.0;
      ELSE 
          Y := 0.5 * ABS(Z);
          IF (Y >= (Z_MAX * 0.5)) THEN 
              X:= 1.0;
          ELSIF (y < 1.0) THEN
              W:= Y * Y;

              X:= ((((((((0.000124818987 * W
                       - 0.001075204047) * W + 0.005198775019) * W
                       - 0.019198292004) * W + 0.059054035642) * W
                       - 0.151968751364) * W + 0.319152932694) * W
                       - 0.531923007300) * W + 0.797884560593) * Y * 2.0;
            ELSE
              Y:= Y-2.0;
              Y:= (((((((((((((-0.000045255659 * Y
                             + 0.000152529290) * Y - 0.000019538132) * Y
                             - 0.000676904986) * Y + 0.001390604284) * Y
                             - 0.000794620820) * Y - 0.002034254874) * Y
                             + 0.006549791214) * Y - 0.010557625006) * Y
                             + 0.011630447319) * Y - 0.009279453341) * Y
                             + 0.005353579108) * Y - 0.002141268741) * Y
                             + 0.000535310849) * Y + 0.999936657524;
          END IF;
      END IF;



      IF (Z>0.0) THEN 
        XXX:=((X + 1.0) * 0.5);
      ELSE
        XXX:= ((1.0 - x) * 0.5);
      END IF;

      RETURN XXX;

END POZ;


FUNCTION EX(X IN NUMBER) RETURN NUMBER IS
BEGIN
  IF (x < -BIGX) THEN
    RETURN 0;
  ELSE
    RETURN EXP(X);
  END IF;
END EX; 


FUNCTION POCHISQ(X IN NUMBER, DF IN NUMBER) RETURN NUMBER IS 
      A NUMBER;
      Y NUMBER;
      S NUMBER;
      E NUMBER;
      C NUMBER;
      Z NUMBER;
      X1 NUMBER;
      EVEN BOOLEAN;                                       /* True if df is an even number */
      LOG_SQRT_PI NUMBER := 0.5723649429247000870717135;  /* log(sqrt(pi)) */
      I_SQRT_PI NUMBER   := 0.5641895835477562869480795;  /* 1 / sqrt(pi) */
  b1 PLS_INTEGER;
  b2 PLS_INTEGER;
  e1 PLS_INTEGER;
  e2 PLS_INTEGER;
BEGIN    
 b1 := DBMS_UTILITY.GET_TIME();
 b2 := DBMS_UTILITY.GET_CPU_TIME();
      X1:=X;
      IF (X1 <= 0.0 OR DF < 1) THEN
          RETURN 1.0;
      END IF;

      A:= 0.5 * X1;
      EVEN:= (MOD(DF,2)=0);

      IF (DF > 1) THEN
          Y := ex(-A);
      END IF;

      IF EVEN THEN
        S:=Y;
      ELSE
        S:=(2.0 * poz(-sqrt(X1)));
      END IF;


      IF (DF > 2) THEN
          X1:= 0.5*(DF-1.0);
          IF EVEN THEN
            Z:=1.0;
          ELSE
            Z:=0.5;
          END IF;

          IF (A > BIGX) THEN
              IF EVEN THEN
                E:=0.0;
              ELSE
                E:=LOG_SQRT_PI;
              END IF;
              C:= LN(A);

              /* Timming snippet */
             e1 := DBMS_UTILITY.GET_TIME() - b1;
             e2 := DBMS_UTILITY.GET_CPU_TIME() - b2;
             --DBMS_OUTPUT.PUT_LINE( '0-GET_TIME elapsed = ' || e1 || ' hsecs.' );
             --DBMS_OUTPUT.PUT_LINE( '0-GET_CPU_TIME elapsed = ' || e2 || ' hsecs.' );
              /* End of Timming snippet */

              WHILE (Z <= X1) 
                LOOP
                  E:= LN(Z) + E;
                  S:=S+EX(C * Z - A - E);
                  Z:=Z+1.0;
              END LOOP;

             e1 := DBMS_UTILITY.GET_TIME() - b1;
             e2 := DBMS_UTILITY.GET_CPU_TIME() - b2;
             --DBMS_OUTPUT.PUT_LINE( '1-GET_TIME elapsed = ' || e1 || ' hsecs. Z= ' || Z );
             --DBMS_OUTPUT.PUT_LINE( '1-GET_CPU_TIME elapsed = ' || e2 || ' hsecs.' );

              RETURN S;
          ELSE
              IF EVEN THEN
                E:=1.0;
              ELSE
                E:=(I_SQRT_PI / sqrt(A));
              END IF;
              C:= 0.0;
              WHILE (Z <= X1) 
                LOOP
                  E:= E * (A / Z);
                  C:= C + E;
                  Z:=Z+ 1.0;
              END LOOP;

             e1 := DBMS_UTILITY.GET_TIME() - b1;
             e2 := DBMS_UTILITY.GET_CPU_TIME() - b2;
             --DBMS_OUTPUT.PUT_LINE( '2-GET_TIME elapsed = ' || e1 || ' hsecs.' );
             --DBMS_OUTPUT.PUT_LINE( '2-GET_CPU_TIME elapsed = ' || e2 || ' hsecs.' );


              RETURN C * Y + S;
          END IF;
      ELSE 

       e1 := DBMS_UTILITY.GET_TIME() - b1;
       e2 := DBMS_UTILITY.GET_CPU_TIME() - b2;
       --DBMS_OUTPUT.PUT_LINE( '3-GET_TIME elapsed = ' || e1 || ' hsecs.' );
       --DBMS_OUTPUT.PUT_LINE( '3-GET_CPU_TIME elapsed = ' || e2 || ' hsecs.' );

          RETURN S;
      END IF;


END POCHISQ;


  /*  CRITCHI  --  Compute critical chi-square value to
                   produce given p.  We just do a bisection
                   search for a value within CHI_EPSILON,
                   relying on the monotonicity of pochisq().  */

FUNCTION CRITCHI(P IN NUMBER, DF IN NUMBER) RETURN NUMBER IS 
    CHI_EPSILON NUMBER:= 0.000001;   /* Accuracy of critchi approximation */
    CHI_MAX NUMBER:= 99999.0;        /* Maximum chi-square value */
    minchisq NUMBER:= 0.0;
    maxchisq NUMBER:= CHI_MAX;
    chisqval NUMBER;
    dummy_count number := 0;
    BEGIN
    IF (p <= 0.0) THEN
        RETURN maxchisq;
    ELSE
        IF (p >= 1.0) THEN
            RETURN 0.0;
        END IF;
    END IF;

    chisqval:= df / sqrt(p);    /* fair first value */
    WHILE ((maxchisq - minchisq) > CHI_EPSILON) 
      LOOP
        if (pochisq(chisqval, df) < p) THEN
            maxchisq:= chisqval;
        ELSE
            minchisq:= chisqval;
        END IF;
      chisqval:= (maxchisq + minchisq) * 0.5;
      dummy_count := dummy_count + 1;
    END LOOP;
    --DBMS_OUTPUT.PUT_LINE('chisqval = ' || chisqval);

    RETURN chisqval;

END CRITCHI;
4

1 回答 1

2

为了将来可能没有耐心浏览所有评论的寻求者的利益,对程序进行了以下优化以使其快速运行。

  1. 所有数值变量都被声明为 BINARY_INTEGER(#)。 了解更多
  2. 函数被声明为确定性的。
  3. 函数被编译为本机 C

(#) 在更现代的数据库版本中,首选 PLS_INTEGER(仅仅是因为 BINARY_INTEGER 已过时且已弃用 - 它在幕后转换为 PLS_INTEGER)。


注意 - 如果 OP 或 @AlexPoole 愿意写一个类似的回复,我会很高兴地支持他们的答案并删除这个答案。

于 2013-07-17T15:35:49.373 回答