2

私の同僚は、JavaScript の逆カイ 2 乗ルーチンを Oracle に変換しました。幸いなことに、javascript ルーチンと同じ結果が返されます。悪いニュースは、結果を返すのに IE や Chrome では 1.5 秒かかるのに対し、Oracle では 23 秒かかることです。その 23 秒のうち、>99% が CPU 時間です。

ルーチンには 2 つのループがあります。テスト対象の値に対して 36 回実行される外側のループと、外側のループの反復ごとに 10,753 回実行される内側のループです。Oracle と同じように、両方の JS で同じループを実行します。内側のループの反復ごとに、EXP 関数と LN 関数が実行されます。どちらも両方の言語に組み込まれています。

私は Oracle コードを解釈済みとネイティブの両方でコンパイルしましたが、ほとんど変更はありませんでした (0.045 秒の差)。

3 つの質問があります。

  1. Oracle が遅いのはなぜですか? どうすれば改善できますか?
  2. Oracleには固有の逆カイ2乗関数がありますか?
  3. 私が使用しているものほど反復ループを必要としない(またはそれほど多くない)逆カイ二乗関数を持っている人はいますか?

おまけの質問は次のとおりです。

PL/SQLで信頼区間を計算するルーチン、またはPL/SQLに簡単に変換できる言語を持っている人はいますか?

リクエストに応じて、少し長いコードを次に示します (メイン ルーチンは、P=0.975 および DF=21507.38 をテストするための CRITCHI です)。

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. 関数はdeterministicとして宣言されました。
  3. 関数はネイティブ C にコンパイルされました。

(#) データベースのより新しいバージョンでは、PLS_INTEGER が優先されます (単純に、BINARY_INTEGER が古くて推奨されていないためです - 裏で PLS_INTEGER に変換されます)。


NB-OPまたは@AlexPooleが同様の応答を書きたい場合は、喜んで彼らの答えに賛成票を投じて、これを削除します。

于 2013-07-17T15:35:49.373 に答える