Производительность PL/SQL - Intra-unit Inlining
ОГЛАВЛЕНИЕ
Intra-unit Inlining
Intra-unit inlining представляет собой подмену вызова подпрограммы на копию кода этой подпрограммы. В результате модифицированный код выполняется быстрее. В Oracle Database 11g компилятор PL/SQL способен идентифицировать вызовы подпрограммы, которую необходимо скопировать (другими словами, подменить на неё) и делает изменения, улучшающие производительность.
Лучше всего это объяснить на примере. Код, показанный ниже, изменяет таблицу BALANCES, вычислив значения на основании баланса счёта. Код проходит по всем записям таблицы, вычисляет результат, и изменяет столбец таблицы с балансом.
create or replace procedure upd_int isФактически, вычисление результата одинаково для всех типов записей, и я поместил логику в отдельную процедуру calc_int() внутри основной процедуры. Это повышает читаемость и сопровождаемость кода, но, к сожалению, это неэффективно.
/* original version */
l_rate_type balances.rate_type%type;
l_bal balances.balance%type;
l_accno balances.accno%type;
l_int_rate number;
procedure calc_int (
p_bal in out balances.balance%type,
p_rate in number
) is
begin
if (p_rate >= 0) then
p_bal := p_bal * (1+(p_rate/12/100));
end if;
end;
begin
for ctr in 1..10000 loop
l_accno := ctr;
select balance, rate_type
into l_bal, l_rate_type
from balances
where accno = l_accno;
select decode(l_rate_type,
'C', 1, 'S', 3, 'M', 5, 0)
into l_int_rate
from dual;
for mth in 1..12 loop
calc_int (l_bal, l_int_rate);
update balances
set balance = l_bal
where accno = l_accno;
end loop;
end loop;
end;
/
Однако, если заменить вызов calc_int() на код calc_int(), получится более быстрая программа, как показано ниже:
create or replace procedure upd_int isЭтот переделанный код отличается от исходного только в части кода для вычисления баланса, который теперь внутри цикла, а не в процедуре calc_int().
/* revised version */
l_rate_type balances.rate_type%type;
l_bal balances.balance%type;
l_accno balances.accno%type;
l_int_rate number;
begin
for ctr in 1..10000 loop
l_accno := ctr;
select balance, rate_type
into l_bal, l_rate_type
from balances
where accno = l_accno;
select decode(l_rate_type,
'C', 1, 'S', 3, 'M', 5, 0)
into l_int_rate
from dual;
for mth in 1..12 loop
-- this is the int calc routine
if (l_int_rate >= 0) then
l_bal := l_bal * (1+(l_int_rate/12/100));
end if;
update balances
set balance = l_bal
where accno = l_accno;
end loop;
end loop;
end;
/
Заметьте, что новая версия может быть быстрее, но это не очень хороший пример практики кодирования. Часть кода, выполняющая вычисление баланса, выполняется один раз для каждой итерации цикла для месяцев, а затем и для каждого номера счёта. Так как эта часть кода повторяется, она более удобна для размещения отдельно, как показано в предыдущей версии upd_int, в процедуре (calc_int). Этот подход делает код модульным, легким в поддержке, и реально читаемым — но также менее эффективным.
Поэтому как можно достичь примирения конфликтующих способов создания кода, сделав код модульным и одновременно быстрым? Так, а можно ли написать код, используя модульный подход (как в первой версии upd_int), а затем позволить компилятору PL/SQL "оптимизировать" его, чтобы он стал выглядеть, как во второй версии кода?
Это можно сделать в Oracle Database 11g. Всё, что требуется сделать - перекомпилировать процедуру с более высоким уровнем оптимизации PL/SQL. Этого можно достичь двумя способами:
- Установить параметр уровня сессии и перекомпилировать процедуру:
SQL> alter session set plsql_optimize_level = 3;
Команда, показанная выше, инструктирует компилятор PL/SQL, чтобы он переписал код во встроенный код.
Session altered.
- Скомпилировать процедуру непосредственно с plsql-установкой.
SQL> alter procedure upd_int
На любую другую процедуру, компилируемую в этой же сессии, это не повлияет. Этот метод лучше применять для обработки inlining, если есть много процедур, которые необходимо скомпилировать в одной сессии.
2 compile
3 plsql_optimize_level = 3
4 reuse settings;
Procedure altered.
create or replace procedure upd_int isЯ добавил строку pragma inline (calc_int, 'YES'); для указания компилятору подменить в коде эту процедуру. Таким же образом можно указать "NO" в этом же месте, чтобы передать компилятору, что не надо подменять эту процедуру, даже если plsql_optimizer_level установлен в значение 3.
l_rate_type varchar2(1);
...
...
begin
pragma inline (calc_int, 'YES');
for ctr in 1..10000 loop
...
...
end;
Inlining делает выполнение кода быстрее. Точная степень улучшения будет зависеть, конечно же, от количества подмен, которые будут сделаны компилятором. В конце этой статьи мы рассмотрим пример с использованием inlining и увидим улучшение производительности в результате его применения.