Производительность PL/SQL - Выполнение эксперимента
ОГЛАВЛЕНИЕ
Выполнение эксперимента
Теперь настало время проверить эти достоинства, повторяя эксперимент. Создадим базовую версию пакета, а затем модифицируем тип данных переменных и директивы компилятора в соответствии с концепциями, изученными перед этим.
Сначала реализуем Euclidean algorithm (алгоритм Евклида) для поиска наибольшего общего делителя для двух чисел. Вот логика функции, со страницы Wiki:
function gcd(a, b)Замерим процессорное время при выполнении пакета при различных комбинациях этих модификаций и сохраним затраченное время. Итак, создаём таблицу для сохранения процессорного времени:
if a = 0 return b
while b ? 0
if a > b
a := a - b
else
b := b - a
return a
create table times(Сделаем три столбца — native, simple и inlining — для обозначения Native compilation, чисел типа simple_integer, и inlined-кода, соответственно. "Y" в столбце означает, что код скомпилирован с использованием этой возможности. Итак, получается запись, которая выглядит так:
native char(1) check (native in ('Y', 'N')) enable,
simple char(1) check (simple in ('Y', 'N')) enable,
inlining char(1) check (inlining in ('Y', 'N')) enable,
centiseconds number not null,
constraint times_pk primary key (simple, inlining, native))
/
NATIVE SIMPLE INLINING CENTISECONDSОна показывает, что программа была скомпилирована в режиме Native compilation, но числа типа simple_integer не использовались, inlining не применялось, и что эта компиляция заняла 100 сёнтисекунд процессорного времени.
------ ------ -------- ------------
Y N N 100
Чтобы использовать только одну копию пакета, для модификации пакета применим условную компиляцию (появилась в Oracle Database 10g Release 2). Ниже показано, как создать пакет:
-- подавляем следующие ожидаемые предупреждения:Пакет имеет достаточно однострочных комментариев для самодокументации кода, поэтому здесь подробные объяснения не требуются. А в общем, функция GCD() имеет два входных параметра и возвращает наибольший общий делитель. Функция exercise_gcd() вызывает функцию GCD() и возвращает время работы процессора (CPU time) в сёнтисекундах. И public-процедураTIME_IT() вызывает функцию EXERCISE_GCD() для соответствующей степени свободы и вставляет запись в таблицу TIMES.
-- inlining of call of procedure 'gcd' was done
-- uncalled procedure "gcd" is removed.
-- unreachable code
-- keyword "native" used as a defined name
alter session set plsql_warnings = 'enable:all, disable:06002, disable:06005, disable:06006, disable:06010'
/
alter session set plsql_ccflags = 'simple:false'
/
create package gcd_test is
procedure time_it;
end gcd_test;
/
create package body gcd_test is
$if $$simple $then
subtype my_integer is simple_integer;
simple constant times.simple%type := 'y';
$else
subtype my_integer is pls_integer not null;
simple constant times.simple%type := 'n';
$end
function gcd(p1 in my_integer, p2 in my_integer) return my_integer is
v1 my_integer := p1; v2 my_integer := p2;
begin
while v2 > 0 loop
if v1 > v2 then
v1 := v1 - v2;
else
v2 := v2 - v1;
end if;
end loop;
return v1;
end gcd;
function exercise_gcd return number is
-- ожидается значение, зависящее от no_of_iterations.
expected_checksum my_integer := 74069926; -- 2475190;
no_of_iterations constant my_integer := 5000; -- 1000;
checksum my_integer := 0;
v my_integer := 0;
t0 number; t1 number;
begin
for warmup in 1..2 loop
checksum := 0;
t0 := dbms_utility.get_cpu_time();
for j in 1..no_of_iterations loop
v := gcd(j, j);
if v <> j then
raise_application_error(-20000, 'logic error: gcd(j, j) <> j');
end if;
checksum := checksum + v;
for k in (j + 1)..no_of_iterations loop
v := gcd(j, k);
if gcd(k, j) <> v then
raise_application_error(-20000, 'logic error: gcd(j, k) <> gcd(k, j)');
end if;
checksum := checksum + v;
end loop;
end loop;
if checksum <> expected_checksum then
raise_application_error(-20000, 'checksum <> expected_checksum: '||checksum);
end if;
t1 := dbms_utility.get_cpu_time();
end loop;
return t1 - t0;
end exercise_gcd;
procedure time_it is
inlining times.inlining%type;
native times.native%type;
centiseconds constant times.centiseconds%type := exercise_gcd();
begin
if lower($$plsql_code_type) = 'native' then
native := 'y';
else
native := 'n';
end if;
if $$plsql_optimize_level = 3 then
inlining := 'y';
else
inlining := 'n';
end if;
insert into times(native, simple, inlining, centiseconds)
values(time_it.native, gcd_test.simple, time_it.inlining, time_it.centiseconds);
commit;
end time_it;
end gcd_test;
/
show errors
Теперь надо несколько раз вызвать пакетную процедуру с различными параметрами и зафиксировать время работы процессора в каждом случае. Выполним это, изменив переменные условной компиляции перед тем, как компилировать пакет:
truncate table timesДля определения, насколько повышается производительность при каждом из этих сценариев, используем следующий код:
/
-- Interpreted ---------------------------------------------
-- Simple:false
-- No Inlining
alter package GCD_Test compile body
PLSQL_Code_Type = interpreted
PLSQL_CCFlags = 'Simple:false'
PLSQL_Optimize_Level = 2 /* no inlining */
reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- inlining
alter package GCD_Test compile body
PLSQL_Code_Type = interpreted
PLSQL_CCFlags = 'Simple:false'
PLSQL_Optimize_Level = 3 /* inlined */
reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- Числа типа simple_integer: используются
-- Без inlining
alter package GCD_Test compile body
PLSQL_Code_Type = interpreted
PLSQL_CCFlags = 'Simple:true'
PLSQL_Optimize_Level = 2
reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- inlined
alter package GCD_Test compile body
PLSQL_Code_Type = interpreted
PLSQL_CCFlags = 'Simple:true'
PLSQL_Optimize_Level = 3
reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- Native -------------------------------------------------
-- Числа типа simple_integer: не используются
-- Без inlining
alter package GCD_Test compile body
PLSQL_Code_Type = native
PLSQL_CCFlags = 'Simple:false'
PLSQL_Optimize_Level = 2
reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- inlined
alter package GCD_Test compile body
PLSQL_Code_Type = native
PLSQL_CCFlags = 'Simple:false'
PLSQL_Optimize_Level = 3
reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- Числа типа simple_integer: используются
-- Без linlining
alter package GCD_Test compile body
PLSQL_Code_Type = native
PLSQL_CCFlags = 'Simple:true'
PLSQL_Optimize_Level = 2
reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- inlined
alter package GCD_Test compile body
PLSQL_Code_Type = native
PLSQL_CCFlags = 'Simple:true'
PLSQL_Optimize_Level = 3
reuse settings
/
begin GCD_Test.Time_It(); end;
/
spool timings.txtНиже показан результат. Он показывает рейтинг процессорного времени по сравнению со значением по умолчанию: без inlining, interpreted compilation, и использование pls_integer.
<<b>>declare
Interp_Pls_Integer_Noinline Times.Centiseconds%type;
Interp_Pls_Integer_Inline Times.Centiseconds%type;
Interp_Simple_Integer_Noinline Times.Centiseconds%type;
Interp_Simple_Integer_Inline Times.Centiseconds%type;
Native_Pls_Integer_Noinline Times.Centiseconds%type;
Native_Pls_Integer_Inline Times.Centiseconds%type;
Native_Simple_Integer_Noinline Times.Centiseconds%type;
Native_Simple_Integer_Inline Times.Centiseconds%type;
procedure Show_Caption(Caption in varchar2) is
begin
DBMS_Output.Put_Line(Chr(10)||Rpad('-', 60, '-')||Chr(10)||Chr(10)||Caption||Chr(10));
end Show_Caption;
procedure Show_Ratio(Var1 in varchar2, Var2 in varchar2, Ratio in number) is
begin
DBMS_Output.Put_Line(Rpad(Var1, 15)||'and '||Rpad(Var2, 14)||To_Char(Ratio, '99.99'));
end Show_Ratio;
begin
select a.Centiseconds
into b.Interp_Pls_Integer_Noinline
from Times a
where a.Native = 'N' and a.Simple = 'N' and a.Inlining = 'N';
select a.Centiseconds
into b.Interp_Pls_Integer_Inline
from Times a
where a.Native = 'N' and a.Simple = 'N' and a.Inlining = 'Y';
select a.Centiseconds
into b.Interp_Simple_Integer_Noinline
from Times a
where a.Native = 'N' and a.Simple = 'Y' and a.Inlining = 'N';
select a.Centiseconds
into b.Interp_Simple_Integer_Inline
from Times a
where a.Native = 'N' and a.Simple = 'Y' and a.Inlining = 'Y';
select a.Centiseconds
into b.Native_Pls_Integer_Noinline
from Times a
where a.Native = 'Y' and a.Simple = 'N' and a.Inlining = 'N';
select a.Centiseconds
into b.Native_Pls_Integer_Inline
from Times a
where a.Native = 'Y' and a.Simple = 'N' and a.Inlining = 'Y';
select a.Centiseconds
into b.Native_Simple_Integer_Noinline
from Times a
where a.Native = 'Y' and a.Simple = 'Y' and a.Inlining = 'N';
select a.Centiseconds
into b.Native_Simple_Integer_Inline
from Times a
where a.Native = 'Y' and a.Simple = 'Y' and a.Inlining = 'Y';
Show_Caption('Benefit of simple_integer');
Show_Ratio('Interpreted', 'no inlining', Interp_Pls_Integer_Noinline / Interp_Simple_Integer_Noinline);
Show_Ratio('Interpreted', 'inlining', Interp_Pls_Integer_Inline / Interp_Simple_Integer_Inline);
Show_Ratio('Native', 'no inlining', Native_Pls_Integer_Noinline / Native_Simple_Integer_Noinline);
Show_Ratio('Native', 'inlining', Native_Pls_Integer_Inline / Native_Simple_Integer_Inline);
Show_Caption('Benefit of inlining');
Show_Ratio('Interpreted', 'pls_integer', Interp_Pls_Integer_Noinline / Interp_Pls_Integer_Inline);
Show_Ratio('Interpreted', 'simple_integer', Interp_Simple_Integer_Noinline / Interp_Simple_Integer_Inline);
Show_Ratio('Native', 'pls_integer', Native_Pls_Integer_Noinline / Native_Pls_Integer_Inline);
Show_Ratio('Native', 'simple_integer', Native_Simple_Integer_NoInline / Native_Simple_Integer_Inline);
Show_Caption('Benefit of native');
Show_Ratio('pls_integer', 'no inlining', Interp_Pls_Integer_Noinline / Native_Pls_Integer_Noinline);
Show_Ratio('pls_integer', 'inlining', Interp_Pls_Integer_Inline / Native_Pls_Integer_Inline);
Show_Ratio('simple_integer', 'no inlining', Interp_Simple_Integer_Noinline / Native_Simple_Integer_Noinline);
Show_Ratio('simple_integer', 'inlining', Interp_Simple_Integer_Inline / Native_Simple_Integer_Inline);
end b;
/
spool off
------------------------------------------------------------Из показанных выше результатов видно, что процессорное время, потраченное на выполнение, было 14.49 для значения по умолчанию по сравнению с Native compilation с inlining и simple_integer — очень приятный результат, по любым стандартам.
Преимущества simple_integer
Интерпретирующая и без inlining 1.00
Интерпретирующая и c inlining 1.00
Native и без inlining 2.19
Native и с inlining 2.79
------------------------------------------------------------
Преимущества inlining
Интерпретирующая и pls_integer 1.07
Интерпретирующая и simple_integer 1.07
Native и pls_integer 1.16
Native и simple_integer 1.48
------------------------------------------------------------
Преимущества Native compilation
pls_integer и без inlining 4.78
pls_integer и inlining 5.18
simple_integer и без inlining 10.53
simple_integer и inlining 14.49