Oracle PLSQL Intra-unit inlining

CREATE OR REPLACE
PROCEDURE inline_test
IS
  s NUMBER:=0;
  FUNCTION get_val(p IN NUMBER) RETURN NUMBER
  IS
  BEGIN
    RETURN (p+1)/100;
  END get_val;
BEGIN
  FOR i IN 1..10000000 LOOP
    s:=s+get_val(i);
  END LOOP;
END inline_test;
/

Одна из интересных новых возможностей Oracle PL/SQL версии 11g это технология Intra-unit inlining. Суть это технологии в том, что если в вашем коде есть вложенные в него процедуры или функции, то с помощью этой технологии можно скомпилировать ваш код таким образом, что эти вложенные процедуры и функции будут убраны в скомпилированном коде, а код этих вложенных процедур или функций будет располагаться непосредственно в теле вашего кода вместо вызова этих вложенных процедур или функций. Вот так немного сложно звучит, однако все на самом деле просто, вложенный код для скорости исполнения незаметно для разработчика на этапе компиляции перемещается внутрь вашего кода, при этом увеличивается скорость исполнения кода из-за отсутствия необходимости вызова вложенных процедур или функций, которые были подменены в коде. Иными словами эта технология призывает выносить повторяющиеся куски кода в отдельные процедуры или функции для лучшей читаемости кода и облегчения его сопровождения без потери производительности. Технология интересная, нас как всегда будет интересовать реальный выигрыш в производительности, который она может обеспечить. Сразу надо оговориться, что подмена вызовов при помощи Intra-unit inlining возможна только для вложенных в ваш код процедур или функций и не будет работать для внешних по отношению к вашему коду процедур или функций. Что немного разочаровывает. Но все таки найти применение этой технологии оптимизации PL/SQL мы можем для некоторых участков кода наших приложений. Попробуем продемонстрировать все это на примере:

Наша тестовая функция вычисляет сумму некоторой функции get_val по значению счетчика цикла.Суть технологии Intra-unit inlining в том, что строка с вызовом функции s:=s+get_val(i) будет в скомпилированном PL/SQL коде заменена на s:=s+(i+1)/100. И это должно выполняться быстрее, чем при помощи отдельной вложенной функции. Вопрос – насколько быстрее?

Запускаем sql*plus компилируем нашу тестовую процедуру, затем замерим время ее выполнения:

EXEC inline_test;
Elapsed: 00:00:05.78

Теперь применяем технологию Intra-unit inlining. Для этого переведем сессию на использование режима PL/SQL оптимизации 3 уровня:

ALTER SESSION SET plsql_optimize_level = 3;

Затем заново прогоним нашу тестовую процедуру. Теперь она скомпилирована с подменой кода. Также можно просто перекомпилировать эту процедуру с указанным уровнем оптимизации, не используя alter session:

ALTER PROCEDURE inline_test compile plsql_optimize_level = 3 reuse settings;

Время выполнения процедуры в режиме Intra-unit inlining:

ALTER PROCEDURE inline_test compile plsql_optimize_level = 3 reuse settings;
EXEC inline_test;
Elapsed: 00:00:03.40

Обратная компиляция процедуры в обычном режиме выполняется следующей командой:

ALTER PROCEDURE inline_test compile plsql_optimize_level = 2 reuse settings;

Скорость выполнения нашей тестовой процедуры при использовании Intra-unit inlining увеличилась на 40 процентов. Неплохой результат. Мы в этом тестовом примере оценили накладные расходы на вынос повторяющегося в цикле кода в отдельную вложенную в код функцию. Эти расходы довольно значительны и при помощи технологии Intra-unit inlining мы можем сохранить как удобство при разработке кода, так и высокую производительность. Можно непосредственно в коде сделать подсказку компилятору о необходимости подмены кода. Это делается с помощью указания компилятору pragma inline, которое делается перед вызовом функции или процедуры, вызов которой необходимо подменить. Синтаксис:

PRAGMA inline (identifier, '<YES | NO>');

Наш пример с использованием этого указания компилятору может быть записан так:

CREATE OR REPLACE
PROCEDURE inline_test
IS
  s NUMBER:=0;
  FUNCTION get_val(p IN NUMBER) RETURN NUMBER
  IS
  BEGIN
    RETURN (p+1)/100;
  END get_val;
BEGIN
  FOR i IN 1..10000000 LOOP
    PRAGMA inline (get_val, 'YES');
    s:=s+get_val(i);
  END LOOP;
END inline_test;
/

Если в параметре pragma inline указать ‘NO’, то подмена вызова никогда не будет выполняться, даже если plsql_optimize_level перед компиляцией установлен в значение 3.Всегда можно увидеть, с каким уровнем оптимизации скомпилирован ваш PL/SQL код. Если этот уровень равен трем, то в этой PL/SQL процедуре, функции или пакете выполняется Intra-unit inlining, за исключением явной отмены этой технологии при помощи pragma inline. Для этого выполните следующий запрос:

SELECT name, plsql_optimize_level
FROM user_plsql_object_settings
Ссылка на основную публикацию