![]() |
+7 (495) 229-0436 | ![]() |
shopadmin@itshop.ru | 119334, г. Москва, ул. Бардина, д. 4, корп. 3 | ![]() |
![]() |
![]() |
|
|
Производительность PL/SQL09.12.2009 15:08
Аруп Нанда
Эта статья рассказывает о том, как inline-подмена, Real native compilation и использование simple_integer могут улучшить производительность кода. Oracle Database 11g представляет несколько отличных новых возможностей для повышения производительности PL/SQL-кода, однако наиболее существенные из них - это native compilation (компиляция с получением исполняемого кода) и intra-unit inlining (подмена). 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. Я измерил время компиляции для каждого случая, оно показано ниже (в секундах).
Результаты говорят сами за себя. При interpreted compilation время компиляции почти одинаковое. Однако при Native compilation время компиляции в 11g меньше примерно на 60% по сравнению с 10g, а это существенное улучшение. Итак, хотя Native compilation в 11g требует дополнительного времени, она намного быстрее, чем Native compilation в 10g. Чтобы найти объекты, скомпилированные с помощью NATIVE, смотрите в представление USER_PLSQL_OBJECT_SETTINGS: SQL> select name, PLSQL_code_type Есть и похожее представление для всех объектов, 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:
Использование 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 InliningIntra-unit inlining представляет собой подмену вызова подпрограммы на копию кода этой подпрограммы. В результате модифицированный код выполняется быстрее. В Oracle Database 11g компилятор PL/SQL способен идентифицировать вызовы подпрограммы, которую необходимо скопировать (другими словами, подменить на неё) и делает изменения, улучшающие производительность. Лучше всего это объяснить на примере. Код, показанный ниже, изменяет таблицу BALANCES, вычислив значения на основании баланса счёта. Код проходит по всем записям таблицы, вычисляет результат, и изменяет столбец таблицы с балансом. create or replace procedure upd_int is Фактически, вычисление результата одинаково для всех типов записей, и я поместил логику в отдельную процедуру calc_int() внутри основной процедуры. Это повышает читаемость и сопровождаемость кода, но, к сожалению, это неэффективно. Однако, если заменить вызов calc_int() на код calc_int(), получится более быстрая программа, как показано ниже: create or replace procedure upd_int is Этот переделанный код отличается от исходного только в части кода для вычисления баланса, который теперь внутри цикла, а не в процедуре calc_int(). Заметьте, что новая версия может быть быстрее, но это не очень хороший пример практики кодирования. Часть кода, выполняющая вычисление баланса, выполняется один раз для каждой итерации цикла для месяцев, а затем и для каждого номера счёта. Так как эта часть кода повторяется, она более удобна для размещения отдельно, как показано в предыдущей версии upd_int, в процедуре (calc_int). Этот подход делает код модульным, легким в поддержке, и реально читаемым - но также менее эффективным. Поэтому как можно достичь примирения конфликтующих способов создания кода, сделав код модульным и одновременно быстрым? Так, а можно ли написать код, используя модульный подход (как в первой версии upd_int), а затем позволить компилятору PL/SQL "оптимизировать" его, чтобы он стал выглядеть, как во второй версии кода? Это можно сделать в Oracle Database 11g. Всё, что требуется сделать - перекомпилировать процедуру с более высоким уровнем оптимизации PL/SQL. Этого можно достичь двумя способами:
create or replace procedure upd_int is Я добавил строку pragma inline (calc_int, 'YES'); для указания компилятору подменить в коде эту процедуру. Таким же образом можно указать "NO" в этом же месте, чтобы передать компилятору, что не надо подменять эту процедуру, даже если plsql_optimizer_level установлен в значение 3. Inlining делает выполнение кода быстрее. Точная степень улучшения будет зависеть, конечно же, от количества подмен, которые будут сделаны компилятором. В конце этой статьи мы рассмотрим пример с использованием inlining и увидим улучшение производительности в результате его применения.
Время компиляцииКонечно, этот процесс оптимизации затрудняет работу компилятора. Но насколько? Чтобы ответить на этот вопрос, я взял код реального приложения, показанный ранее, и скомпилировал его в различных комбинациях с inlining/без inlining и interpreted/native. Вот время компиляции:
Результаты говорят сами за себя. При компиляции с использованием inline, время компиляции выше совсем чуть-чуть (около 4%) в режиме interpreted. В режиме native, оно вышеl,но и разница больше - около 12%. Таким образом, целесообразно компилировать приложение в режиме inline/interpreted во время разработки, а затем в режиме native на стадии окончания. Использование inlining добавляет время компиляции несущественно, поэтому при разработке это время ни на что не влияет. Теперь мы подошли к важному вопросу: Раз код не менялся, как можно подтвердить, что код был подменён? Это можно сделать с помощью переменной сессии: alter session set plsql_warnings = 'enable:all'; Теперь, после пересоздания процедуры: SQL> @upd_int Обратите внимание на последнюю строку, которая подтверждает, что процедура calc_int была подменена. SQL> show error Если вы хотите найти объекты, которые были скомпилированы с этим уровнем, можно выполнить запрос к представлению USER_PLSQL_OBJECT_SETTINGS: sql> select name, plsql_optimize_level Есть и похожее представление для всех объектов: DBA_PLSQL_OBJECT_SETTINGS. Помните, intra-unit inlining означает, что подменяются только те процедуры, которые расположены внутри блока. Внешние подпрограммы не подменяются. Выполнение экспериментаТеперь настало время проверить эти достоинства, повторяя эксперимент. Создадим базовую версию пакета, а затем модифицируем тип данных переменных и директивы компилятора в соответствии с концепциями, изученными перед этим. Сначала реализуем Euclidean algorithm (алгоритм Евклида) для поиска наибольшего общего делителя для двух чисел. Вот логика функции, со страницы Wiki: function gcd(a, b) Замерим процессорное время при выполнении пакета при различных комбинациях этих модификаций и сохраним затраченное время. Итак, создаём таблицу для сохранения процессорного времени: create table times( Сделаем три столбца - native, simple и inlining - для обозначения Native compilation, чисел типа simple_integer, и inlined-кода, соответственно. "Y" в столбце означает, что код скомпилирован с использованием этой возможности. Итак, получается запись, которая выглядит так: NATIVE SIMPLE INLINING CENTISECONDS Она показывает, что программа была скомпилирована в режиме Native compilation, но числа типа simple_integer не использовались, inlining не применялось, и что эта компиляция заняла 100 сёнтисекунд процессорного времени. Чтобы использовать только одну копию пакета, для модификации пакета применим условную компиляцию (появилась в Oracle Database 10g Release 2). Ниже показано, как создать пакет: -- подавляем следующие ожидаемые предупреждения: Пакет имеет достаточно однострочных комментариев для самодокументации кода, поэтому здесь подробные объяснения не требуются. А в общем, функция GCD() имеет два входных параметра и возвращает наибольший общий делитель. Функция exercise_gcd() вызывает функцию GCD() и возвращает время работы процессора (CPU time) в сёнтисекундах. И public-процедураTIME_IT() вызывает функцию EXERCISE_GCD() для соответствующей степени свободы и вставляет запись в таблицу TIMES. Теперь надо несколько раз вызвать пакетную процедуру с различными параметрами и зафиксировать время работы процессора в каждом случае. Выполним это, изменив переменные условной компиляции перед тем, как компилировать пакет: truncate table times Для определения, насколько повышается производительность при каждом из этих сценариев, используем следующий код: spool timings.txt Ниже показан результат. Он показывает рейтинг процессорного времени по сравнению со значением по умолчанию: без inlining, interpreted compilation, и использование pls_integer. ------------------------------------------------------------ Из показанных выше результатов видно, что процессорное время, потраченное на выполнение, было 14.49 для значения по умолчанию по сравнению с Native compilation с inlining и simple_integer - очень приятный результат, по любым стандартам. ЗаключениеТеперь вы можете оценить важность и полезность этих новых возможностей. Итог:
Ссылки по теме |
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
О нас |
Интернет-магазин ITShop.ru предлагает широкий спектр услуг информационных технологий и ПО.
На протяжении многих лет интернет-магазин предлагает товары и услуги, ориентированные на бизнес-пользователей и специалистов по информационным технологиям. Хорошие отзывы постоянных клиентов и высокий уровень специалистов позволяет получить наивысший результат при совместной работе. В нашем магазине вы можете приобрести лицензионное ПО выбрав необходимое из широкого спектра и ассортимента по самым доступным ценам. Наши менеджеры любезно помогут определиться с выбором ПО, которое необходимо именно вам. Также мы проводим учебные курсы. Мы приглашаем к сотрудничеству учебные центры, организаторов семинаров и бизнес-тренингов, преподавателей. Сфера сотрудничества - продвижение бизнес-тренингов и курсов обучения по информационным технологиям.
|
119334, г. Москва, ул. Бардина, д. 4, корп. 3 +7 (495) 229-0436 shopadmin@itshop.ru |
|
© ООО "Interface Ltd." Продаем программное обеспечение с 1990 года |