اوراکل از زمان اوراکل 8i یک ماشین مجازی جاوا را به عنوان بخشی از پایگاه داده‌اش منتشر کرده است. خاطرم هست که چند سال پیش از کسی شنیدم که می‌گفت JVM اوراکل از PL/SQL در اجرای عملیات‌های ریاضی سریع‌تر است، اما هیچوقت به این گفته به شکل جدی نگاه نکردم. در این نوشته من یک مقایسه‌ی تطبیقی بر مبنای چند عملیات ریاضی پایه میان JVM اوراکل و PL/SQL انجام می‌دهم.
توجه کنید که من یک برنامه‌نویس PL/SQL هستم و از همین دید به قضیه نگاه می‌کنم. دلیل اینکه تمام آزمایش‌ها هم از PL/SQL فراخوانده شده‌اند همین است.


لازم است که ما قبل از هر چیز دیگری، کمی جاوا به پایگاه داده‌مان اضافه کنیم تا توانایی اجرای عملیات‌های ریاضی را داشته باشد. کد جاوایی که در ادامه نوشته شده‌است، کلاسی به نام «ریاضیات» تعریف می‌کند که شامل پنج عملکرد ایستا برای اجرا کردن پنج عملیاتی است (+,-,*,/,Mod) که قرار است مقایسه‌شان کنیم.

CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "Mathematics" AS
import java.lang.*;

public class Mathematics
{

public static int addFn (int Number1, int Number2)
{
return Number1 + Number2;
}

public static int subtractFn (int Number1, int Number2)
{
return Number1 - Number2;
}

public static int multiplyFn (int Number1, int Number2)
{
return Number1 * Number2;
}

public static int divideFn (int Number1, int Number2)
{
return Number1 / Number2;
}

public static int modFn (int Number1, int Number2)
{
return Number1 % Number2;
}

};
/

در مرحله‌ی بعد ما یک PACKAGE تعریف می‌کنیم که شامل مشخصات فراخوانی پنج عملکرد جاوایی می‌شود که پیش‌تر تعریفشان کرده‌بودیم، و همین‌طور فرآیندی برای آزمایش‌کردن آن‌ها به شکل واقعی.

CREATE OR REPLACE PACKAGE maths_test AS

FUNCTION addFn (p_number1 IN NUMBER, p_number2 IN NUMBER) RETURN NUMBER
AS LANGUAGE JAVA
NAME 'Mathematics.addFn (int, int) return int';

FUNCTION subtractFn (p_number1 IN NUMBER, p_number2 IN NUMBER) RETURN NUMBER
AS LANGUAGE JAVA
NAME 'Mathematics.subtractFn (int, int) return int';

FUNCTION multiplyFn (p_number1 IN NUMBER, p_number2 IN NUMBER) RETURN NUMBER
AS LANGUAGE JAVA
NAME 'Mathematics.multiplyFn (int, int) return int';

FUNCTION divideFn (p_number1 IN NUMBER, p_number2 IN NUMBER) RETURN NUMBER
AS LANGUAGE JAVA
NAME 'Mathematics.divideFn (int, int) return int';

FUNCTION modFn (p_number1 IN NUMBER, p_number2 IN NUMBER) RETURN NUMBER
AS LANGUAGE JAVA
NAME 'Mathematics.modFn (int, int) return int';

PROCEDURE test (p_operation IN VARCHAR2);

END maths_test;
/

بدنه‌ی PACKAGE تنها شامل تعریف فرآیند «آزمایش» می‌شود که سرعت PL/SQL را با نسخه‌های جاوای همان عملکردها مقایسه می‌کند.

CREATE OR REPLACE PACKAGE BODY maths_test AS

PROCEDURE test (p_operation IN VARCHAR2) AS
l_start SIMPLE_INTEGER := 0; -- Use PLS_INTEGER prior to 11g
l_val SIMPLE_INTEGER := 0; -- Use PLS_INTEGER prior to 11g
l_loops NUMBER := 1000000;
BEGIN
l_start := DBMS_UTILITY.get_time;

FOR i IN 1 .. l_loops LOOP
CASE LOWER(p_operation)
WHEN '+' THEN l_val := i + 2;
WHEN '-' THEN l_val := i - 2;
WHEN '*' THEN l_val := i * 2;
WHEN '/' THEN l_val := i / 2;
WHEN 'mod' THEN l_val := MOD(i, 2);
END CASE;
END LOOP;

DBMS_OUTPUT.put_line('PL/SQL (' || p_operation || '): ' || (DBMS_UTILITY.get_time - l_start) || ' hsecs');

l_start := DBMS_UTILITY.get_time;

FOR i IN 1 .. l_loops LOOP
CASE LOWER(p_operation)
WHEN '+' THEN l_val := addFn(i, 2);
WHEN '-' THEN l_val := subtractFn(i, 2);
WHEN '*' THEN l_val := multiplyFn(i, 2);
WHEN '/' THEN l_val := divideFn(i, 2);
WHEN 'mod' THEN l_val := modFn(i, 2);
END CASE;
END LOOP;

DBMS_OUTPUT.put_line('Java (' || p_operation || '): ' || (DBMS_UTILITY.get_time - l_start) || ' hsecs');
END test;

END maths_test;
/

خروجی‌ای که به دنبال نوشته شده‌است به روشنی نشان می‌دهد که JVM اوراکل در انجام عملیات‌های ریاضی، مشخصا کندتر از PL/SQL عمل می‌کند.

SQL> SET SERVEROUTPUT ON
SQL> EXEC maths_test.test('+');
PL/SQL (+): 9 hsecs
Java (+): 2260 hsecs

PL/SQL procedure successfully completed.

SQL> EXEC maths_test.test('-');
PL/SQL (-): 12 hsecs
Java (-): 2314 hsecs

PL/SQL procedure successfully completed.

SQL> EXEC maths_test.test('*');
PL/SQL (*): 16 hsecs
Java (*): 2332 hsecs

PL/SQL procedure successfully completed.

SQL> EXEC maths_test.test('/');
PL/SQL (/): 53 hsecs
Java (/): 2417 hsecs

PL/SQL procedure successfully completed.

SQL> EXEC maths_test.test('mod');
PL/SQL (mod): 70 hsecs
Java (mod): 2360 hsecs

PL/SQL procedure successfully completed.

SQL>

آزمایش ما برای JVM کاملا هم جوانمردانه نیسن، چرا که ما به طور مداوم در حال پرش از PL/SQL به جاوا و بالعکس بودیم. اما اگر آن را جوری بارنویسی کنیم که تمام بخش جاوای آزمایشمان به شکل جاوا اجرا شود چه؟ کلاس جاوای به‌نویسی‌شده‌مان چیزی شبیه این خواهد بود.

CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "Mathematics" AS
import java.lang.*;

public class Mathematics
{

public static void testJava (int operation, int loops)
{
int val;
for (int i=1; i <= loops; i++) {
switch (operation) {
case 1: val = i + 2; break;
case 2: val = i - 2; break;
case 3: val = i * 2; break;
case 4: val = i / 2; break;
case 5: val = i % 2; break;
}
}
}

};
/

سربرگ PACKAGE حالا تنها به یک تنظیم برای فراخوانی جاوا نیاز دارد.

CREATE OR REPLACE PACKAGE maths_test AS

PROCEDURE testjava (p_operation IN NUMBER, p_loops IN NUMBER)
AS LANGUAGE JAVA
NAME 'Mathematics.testJava (int, int)';

PROCEDURE test (p_operation IN VARCHAR2);

END maths_test;
/

بدنه‌ی PACKAGE تنها به چند تغییر دیگر احتیاج دارد. اول، بدنه پارامتر عملکرد را به یک عدد تبدیل می‌کند تا بشود آن را به جاوا سپرد و از آن در یک گذاره‌ی سوییچ استفاده کرد. کمی احمقانه به نظر می‌رسد، اما چنین کاری از آشفتگی‌های اطراف جاوا می‌کاهد. دوم، یک فراخوانی هم به کد جاوایی که عملیات را می‌گذراند و تعداد دفعاتی که باید فرآیند را تکرار کند، وجود دارد.

CREATE OR REPLACE PACKAGE BODY maths_test AS

PROCEDURE test (p_operation IN VARCHAR2) AS
l_start PLS_INTEGER := 0;
l_val PLS_INTEGER := 0;
l_loops NUMBER := 1000000;
l_operation NUMBER;
BEGIN
-- Translate the original operaton string to number so I can use switch in Java.
CASE LOWER(p_operation)
WHEN '+' THEN l_operation := 1;
WHEN '-' THEN l_operation := 2;
WHEN '*' THEN l_operation := 3;
WHEN '/' THEN l_operation := 4;
WHEN 'mod' THEN l_operation := 5;
END CASE;

l_start := DBMS_UTILITY.get_time;

FOR i IN 1 .. l_loops LOOP
CASE l_operation
WHEN 1 THEN l_val := i + 2;
WHEN 2 THEN l_val := i - 2;
WHEN 3 THEN l_val := i * 2;
WHEN 4 THEN l_val := i / 2;
WHEN 5 THEN l_val := MOD(i, 2);
END CASE;
END LOOP;

DBMS_OUTPUT.put_line('PL/SQL (' || p_operation || '): ' || (DBMS_UTILITY.get_time - l_start) || ' hsecs');

l_start := DBMS_UTILITY.get_time;

testJava(l_operation, l_loops);

DBMS_OUTPUT.put_line('Java (' || p_operation || '): ' || (DBMS_UTILITY.get_time - l_start) || ' hsecs');
END test;

END maths_test;
/

حالا آزمایش‌ها نشان می‌دهند که JVM اوراکل، لوپ‌ها و فرآیندهای ریاضی پایه را سریعتر از PL/SQL انجام می‌دهد.

SQL> SET SERVEROUTPUT ON
SQL> EXEC maths_test.test('+');
PL/SQL (+): 7 hsecs
Java (+): 4 hsecs

PL/SQL procedure successfully completed.

SQL> EXEC maths_test.test('-');
PL/SQL (-): 10 hsecs
Java (-): 4 hsecs

PL/SQL procedure successfully completed.

SQL> EXEC maths_test.test('*');
PL/SQL (*): 15 hsecs
Java (*): 4 hsecs

PL/SQL procedure successfully completed.

SQL> EXEC maths_test.test('/');
PL/SQL (/): 40 hsecs
Java (/): 6 hsecs

PL/SQL procedure successfully completed.

SQL> EXEC maths_test.test('mod');
PL/SQL (mod): 62 hsecs
Java (mod): 5 hsecs

PL/SQL procedure successfully completed.

SQL>

این نتایج بر روی نسخه‌های مختلف پایگاه داده، مشابه هستند  (9.2, 10.2, 11.1 , 11.2)
آیا این نکته بدین معناست که شما باید تمام PL/SQL خود را در جاوا بازنویسی کنید؟ نه. ما در بررسی‌مان تعامل پایگاه داده را در نظر نگرفته ایم، که در حقیقت همان چیزی است که PL/SQL برای آن ساخته شده است. ما همچنین مقیاس‌پذیری یا تاثیر حضور چند کاربر بر اوراکل را بررسی نکرده‌ایم. البته من از مشکل مربوط به کامپایل‌کردن عادی هم صرف‌نظر کرده‌ام، چرا که هم جاوا و هم PL/SQL می‌توانند به صورت عادی کامپایل شوند.
در کل فکر کردم شاید جالب باشه.