Производительность PL/SQL

ОГЛАВЛЕНИЕ

Native compilation – это не совсем новая возможность, однако теперь нет «узких» мест её использования, например, установка компилятора C (Oracle назвал эту замечательную возможность "Real Native Compilation"). Кроме того, новый тип данных simple_integer делает выполнение кода лучше при Native compilation. Intra-unit inlining – это техника оптимизации, применяемая к PL/SQL-коду во время компиляции для создания эффективного кода. В этой статье будут рассмотрены некоторые случаи использования этих новых возможностей. Будет также проверена их производительность при различных сценариях: когда используется Native compilation, когда используются числа типа simple_integer, когда используется inlining и их различные комбинации. Real Native Compilation

Вспомните Native compilation в Oracle9i Database Release 2; она делает выполнение PL/SQL-программ намного быстрее по сравнению с интерпретируемыми формами. С другой стороны, освоение было медленным, так как многие системные администраторы сопротивлялись установке требуемого компилятора C на производственный сервер базы данных. Кроме того, такие компиляторы требуют установки параметра plsql_native_library_dir с директорией для промежуточных файлов OS. В Oracle Database 11g можно выполнять Native compilation без компилятора C на сервере и без установки параметра. Всё, что необходимо сделать, это установить параметр сессии перед созданием или перекомпиляцией хранимого кода:

alter session set plsql_code_type = native;
... здесь выполняется компиляция ...

Native compilation выполняется дольше, чем interpreted compilation, но так как этот процесс в Oracle Database 11g намного быстрее, то разница может оказаться незаметной. Лучше всего применять interpreted compilation во время обычного цикла разработки и Native compilation, когда разработка завершена.

Как часть миграции на 11g, я выполнил эксперимент с реальным кодом из диагностического приложения над очень большим пакетом из 5 827 строк. Я скомпилировал его в режиме Native compilation на существующей базе данных 10g и сделал то же самое на 11g, а затем повторил эти действия в режиме interpreted compilation. Каждая из этих компиляций была сделана с параметром plsql_optimize_level, равным 2. Я измерил время компиляции для каждого случая, оно показано ниже (в секундах).


10g

11g

Interpreted

1.66

1.64

Native

4.66

2.81

Результаты говорят сами за себя. При interpreted compilation время компиляции почти одинаковое. Однако при Native compilation время компиляции в 11g меньше примерно на 60% по сравнению с 10g, а это существенное улучшение. Итак, хотя Native compilation в 11g требует дополнительного времени, она намного быстрее, чем Native compilation в 10g.

Чтобы найти объекты, скомпилированные с помощью NATIVE, смотрите в представление USER_PLSQL_OBJECT_SETTINGS:

SQL> select name, PLSQL_code_type
  2> from user_plsql_object_settings;
 
NAME                 PLSQL_CODE_TYPE
-------------------- ---------------
DO_CALC              NATIVE
PERFECT_TRIANGLES    NATIVE
PRIME_NUMBERS        NATIVE
PRIME_NUMBERS        NATIVE
SOME_MATH            INTERPRETED
TR_BOOKINGS_TRACK    INTERPRETED
TR_SALGRADE_COMP     INTERPRETED
UPD_INT              NATIVE

Есть и похожее представление для всех объектов, DBA_PLSQL_OBJECT_SETTINGS.


Новый тип данных: Simple_Integer

Достоинства Native compilation ещё больше очевидны при использовании нового типа данных, simple_integer. Технически это не настоящий тип данных, а скорее подтип pls_integer. Этот подтип создан для улучшения машинных вычислений по сравнению с программными вычислениями. Когда simple_integer используется одновременно с Native compilation, производительность будет намного лучше. В эксперименте, который будет показан дальше, вы увидите, почему.

Так как simple_integer является подтипом pls_integer, он наследует его свойства как 32-битного целого числа со знаком и может быть целым числом от - 2,147,483,648 до 2,147,483,647. Однако он отличается от pls_integer следующим: он не допускает значения null, но допускает переполнение — когда значение превышает максимум, оно сбрасывается, а ошибка не появляется.

Этот тип данных можно синтаксически использовать во всех тех же местах, где и pls_integer, но необходимо внимательно следить за разницей; дополнительные свойства simple_integer в некоторых случаях могут оказаться неуместными.

Давайте проверим несколько потенциальных задач, где следовало бы заменить pls_integer на simple_integer:

  • Эта переменная не может быть null, поэтому если не написать так, как показано ниже:
    num1    simple_integer:= 1; 
    а написать так:
    num1    simple_integer; 
    то будет получена ошибка компиляции:
    PLS-00218: a variable declared NOT NULL must have an initialization assignment 

    Если установить значение переменной в NULL внутри программы, например, так:

    num1 := NULL; 

    будет получена ошибка компиляции:

    PLS-00382: expression is of wrong type 
    Избегайте этих сообщений об ошибках, которые могут быть не видны с точки зрения точной сути ошибки. Если программа ожидает установки переменной в null, то не следует описывать переменную, как simple_integer.
  • Другой важный момент в применении simple_integer заключается в том, что используется сброс значений при достижении максимальной и минимальной границ. Помните, максимальное положительное значение pls_integer, это 2147483647. Что случится, если попытаться сохранить значение, которое больше? Посмотрите демонстрационный пример:
    declare v1 pls_integer := 2147483647; 
    begin
    v1 := v1 + 1;
    dbms_output.put_line('v1='||v1);
    end;
    /
    Будет получена ошибка:
    declare 
    *
    ERROR at line 1:
    ORA-01426: numeric overflow
    ORA-06512: at line 4
    Ошибка очевидна и вполне уместна; вы попытались превысить максимальное значение, допустимое для типа данных. Если вместо pls_integer использовать simple_integer:
    declare v1 
    simple_integer := 2147483647;
    begin
    v1 := v1 + 1;
    dbms_output.put_line('v1='||v1);
    end;
    /
    Результат будет следующим:
    v1=-2147483648 
    Заметьте, что значение (-2147483648), это минимальное значение simple_integer. Когда вы прибавляете к максимальному значению (2147483647), значение просто сбрасывается до минимального — это особенность simple_integer. Помните об этом поведении.


Использование Native compilation с типом Simple_Integer

Как видите, simple_integer не могут использоваться в любом месте; необходимо быть внимательными при учёте дополнительных условий (особенно возможном сбросе значений) перед их применением. Поэтому simple_integer создан для Native compilation. В режиме interpreted compilation эффекта производительности может не быть (но он и вреда нет, как будет видно дальше). В режиме Native compilation, производительность simple_integer намного более существенна.

Большинство бизнес-приложений на PL/SQL жестко связаны с SQL, поэтому эти приложения не увидят значительного изменения производительности при Native compilation. В "прошлой жизни " я разрабатывал инструмент для планирования возможностей базы данных с использованием PL/SQL, включающего много числовых и статистических вычислений в несколько тысяч строк кода. При Native compilation было видно существенное увеличение в производительности. Числа типа Simple_integer не были доступны в то время, но если бы были, то они добавили бы ещё большей производительности.


Intra-unit Inlining

Intra-unit inlining представляет собой подмену вызова подпрограммы на копию кода этой подпрограммы. В результате модифицированный код выполняется быстрее. В Oracle Database 11g компилятор PL/SQL способен идентифицировать вызовы подпрограммы, которую необходимо скопировать (другими словами, подменить на неё) и делает изменения, улучшающие производительность.

Лучше всего это объяснить на примере. Код, показанный ниже, изменяет таблицу BALANCES, вычислив значения на основании баланса счёта. Код проходит по всем записям таблицы, вычисляет результат, и изменяет столбец таблицы с балансом.

create or replace procedure upd_int is
/* 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() на код calc_int(), получится более быстрая программа, как показано ниже:

create or replace procedure upd_int is
/* 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;
/
Этот переделанный код отличается от исходного только в части кода для вычисления баланса, который теперь внутри цикла, а не в процедуре calc_int().

Заметьте, что новая версия может быть быстрее, но это не очень хороший пример практики кодирования. Часть кода, выполняющая вычисление баланса, выполняется один раз для каждой итерации цикла для месяцев, а затем и для каждого номера счёта. Так как эта часть кода повторяется, она более удобна для размещения отдельно, как показано в предыдущей версии upd_int, в процедуре (calc_int). Этот подход делает код модульным, легким в поддержке, и реально читаемым — но также менее эффективным.

Поэтому как можно достичь примирения конфликтующих способов создания кода, сделав код модульным и одновременно быстрым? Так, а можно ли написать код, используя модульный подход (как в первой версии upd_int), а затем позволить компилятору PL/SQL "оптимизировать" его, чтобы он стал выглядеть, как во второй версии кода?

Это можно сделать в Oracle Database 11g. Всё, что требуется сделать - перекомпилировать процедуру с более высоким уровнем оптимизации PL/SQL. Этого можно достичь двумя способами:

  • Установить параметр уровня сессии и перекомпилировать процедуру:
    SQL> alter session set plsql_optimize_level = 3; 
    Session altered.
    Команда, показанная выше, инструктирует компилятор PL/SQL, чтобы он переписал код во встроенный код.
  • Скомпилировать процедуру непосредственно с plsql-установкой.
    SQL> alter procedure upd_int
      2  compile
      3  plsql_optimize_level = 3
      4  reuse settings;
     
    Procedure altered.
    На любую другую процедуру, компилируемую в этой же сессии, это не повлияет. Этот метод лучше применять для обработки inlining, если есть много процедур, которые необходимо скомпилировать в одной сессии.
create or replace procedure upd_int is
    l_rate_type     varchar2(1);
...
...
begin
    pragma inline (calc_int, 'YES');
    for ctr in 1..10000 loop
...
...
end;
Я добавил строку pragma inline (calc_int, 'YES'); для указания компилятору подменить в коде эту процедуру. Таким же образом можно указать "NO" в этом же месте, чтобы передать компилятору, что не надо подменять эту процедуру, даже если plsql_optimizer_level установлен в значение 3.

Inlining делает выполнение кода быстрее. Точная степень улучшения будет зависеть, конечно же, от количества подмен, которые будут сделаны компилятором. В конце этой статьи мы рассмотрим пример с использованием inlining и увидим улучшение производительности в результате его применения.


Время компиляции

Конечно, этот процесс оптимизации затрудняет работу компилятора. Но насколько?

Чтобы ответить на этот вопрос, я взял код реального приложения, показанный ранее, и скомпилировал его в различных комбинациях с inlining/без inlining и interpreted/native. Вот время компиляции:

 

Inlined

Not inlined

Interpreted

1.70

1.64

Native

3.15

2.81


Результаты говорят сами за себя. При компиляции с использованием inline, время компиляции выше совсем чуть-чуть (около 4%) в режиме interpreted. В режиме native, оно вышеl,но и разница больше — около 12%. Таким образом, целесообразно компилировать приложение в режиме inline/interpreted во время разработки, а затем в режиме native на стадии окончания. Использование inlining добавляет время компиляции несущественно, поэтому при разработке это время ни на что не влияет.

Теперь мы подошли к важному вопросу: Раз код не менялся, как можно подтвердить, что код был подменён? Это можно сделать с помощью переменной сессии:

alter session set plsql_warnings = 'enable:all'; 
Теперь, после пересоздания процедуры:
SQL> @upd_int   
SP2-0804: Procedure created with compilation warnings
Обратите внимание на последнюю строку, которая подтверждает, что процедура calc_int была подменена.
SQL> show error
Errors for PROCEDURE UPD_INT:
 
LINE/COL ERROR
-------- -----------------------------------------------------------------
7/5      PLW-06006: uncalled procedure "CALC_INT" is removed.
28/13    PLW-06005: inlining of call of procedure 'CALC_INT' was done
Если вы хотите найти объекты, которые были скомпилированы с этим уровнем, можно выполнить запрос к представлению USER_PLSQL_OBJECT_SETTINGS:
sql> select name, plsql_optimize_level
  2> from user_plsql_object_settings;
 
NAME                 PLSQL_OPTIMIZE_LEVEL
-------------------- --------------------
DO_CALC                                 2
PERFECT_TRIANGLES                       2
TR_BOOKINGS_TRACK                       2
TR_SALGRADE_COMP                        2
UPD_INT                                 3
... и так далее ...
Есть и похожее представление для всех объектов: DBA_PLSQL_OBJECT_SETTINGS.

Помните, intra-unit inlining означает, что подменяются только те процедуры, которые расположены внутри блока. Внешние подпрограммы не подменяются.


Выполнение эксперимента

Теперь настало время проверить эти достоинства, повторяя эксперимент. Создадим базовую версию пакета, а затем модифицируем тип данных переменных и директивы компилятора в соответствии с концепциями, изученными перед этим.

Сначала реализуем 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        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 — для обозначения Native compilation, чисел типа simple_integer, и inlined-кода, соответственно. "Y" в столбце означает, что код скомпилирован с использованием этой возможности. Итак, получается запись, которая выглядит так:
NATIVE SIMPLE INLINING CENTISECONDS
------ ------ -------- ------------
Y      N      N                 100
Она показывает, что программа была скомпилирована в режиме Native compilation, но числа типа simple_integer не использовались, inlining не применялось, и что эта компиляция заняла 100 сёнтисекунд процессорного времени.

Чтобы использовать только одну копию пакета, для модификации пакета применим условную компиляцию (появилась в Oracle Database 10g Release 2). Ниже показано, как создать пакет:

-- подавляем следующие ожидаемые предупреждения:
--   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
Пакет имеет достаточно однострочных комментариев для самодокументации кода, поэтому здесь подробные объяснения не требуются. А в общем, функция GCD() имеет два входных параметра и возвращает наибольший общий делитель. Функция exercise_gcd() вызывает функцию GCD() и возвращает время работы процессора (CPU time) в сёнтисекундах. И public-процедураTIME_IT() вызывает функцию EXERCISE_GCD() для соответствующей степени свободы и вставляет запись в таблицу TIMES.

Теперь надо несколько раз вызвать пакетную процедуру с различными параметрами и зафиксировать время работы процессора в каждом случае. Выполним это, изменив переменные условной компиляции перед тем, как компилировать пакет:

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
<<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
Ниже показан результат. Он показывает рейтинг процессорного времени по сравнению со значением по умолчанию: без inlining, interpreted compilation, и использование pls_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
Из показанных выше результатов видно, что процессорное время, потраченное на выполнение, было 14.49 для значения по умолчанию по сравнению с Native compilation с inlining и simple_integer — очень приятный результат, по любым стандартам.


Заключение

Теперь вы можете оценить важность и полезность этих новых возможностей. Итог:

  • Тип данных simple_integer может синтаксически использоваться там же, где и pls_integer, но возможен сброс значений и есть требование в отношении отсутствия null. Это означает, что числа типа simple_integer не могут использоваться везде. Если приложение нет превышения максимума или понижения ниже минимума, то их хорошо использовать с Native compilation.
  • Достоинства simple_integer очень заметны при использовании Native compilation и минимальны при использовании interpreted compilation. Заметьте, что даже при interpreted compilation тип simple_integer больше полезен, чем вреден.
  • Преимущества inlining также более заметны при использовании Native compilation по сравнению с interpreted compilation. Это труднее объяснить. Простейший, краткий код, позволяющий приблизиться к компиляции в машинные коды, имеет больше шансов на оптимизацию в режиме native, а в режиме interpreted она невозможна.
  • Преимущества Native compilation больше, когда программа для этого подходит — это значит, что она не выполняет SQL и не использует Oracle number, date и так далее. Заметьте, что даже когда эти условия соблюдены, выигрыш может существенно различаться.
  • Выбор Native compilation и inlining в Oracle Database 11g не требует умственных усилий. Есть только одна причина против, это дополнительное время компиляции (может быть в процессе разработки на ранних стадиях).
  • В перспективе использование simple_integer будет редким. Однако когда момент настанет, им следует воспользоваться.
Arup Nanda