DBMS 1

DA, SQL, DB보안 등 실무자를 위한 위한 DB기술 바이블!

PL/SQL 성능

DBMS 1
Oracle 가이드
11g, DBA를 위한 신기능
PL/SQL 성능
작성자
dataonair
작성일
2021-02-17 17:01
조회
1641

PL/SQL 성능

간략한 개요

코드 인라이닝, 진정한 네이티브 컴파일 및 단순 인터저 사용으로 코드 성능을 향상시키는 사용 방법을 알아보도록 하겠습니다

Oracle Database 11g는 PL/SQL 코드 성능 향상을 위한 많은 매력적인 새로운 기능을 가지고 있지만, 가장 극적인 것은 네이티브 컴파일과 인트라 유닛 인라이닝입니다네이티브 캄파일은 그 자체로는 새로운 기능이 아니지만, 이를 사용하기 위한 전제 조건이 없다는 것-C 컴파일러 설치 등-은 진정 새로운 것입니다. (오라클은 이 향상된 기능을 "Real Native Compilation"이라고 부릅니다.) 또한, 데이터 유형과 simple_integer는 네이티브 컴파일에서 코드 성능을 향상시킵니다. 인트라 유닛 인라이닝은 효율적 코드 생산을 위해 컴파일 시 PL/SQL 코드에 적용되는 최적화 기술입니다..

이 문서에서, 우리는 이들 새로운 기능의 적절한 사용 방법을 배울 것입니다. 우리는 또한 서로 다른 시나리오에서의 성능을 확인할 것입니다: 언제 네이티브하게 컴파일되는지, 언제 단순 인터저를 사용하는지, 언제 프로그램을 인라이닝하는지 등에 관한 것입니다.

Real Native Compilation

Oracle9i Database Release 2에서 소개된 네이티브 컴파일을 기억하실 것입니다; 이는 번역된 형식과 비교해 PL/SQL 실행을 훨씬 빠르게 해줍니다. 이러한 장점에도 불구하고, 많은 시스템 관리자들이 C 컴파일러를 프로덕션 데이터베이스 서버에 설치하는 것을 꺼려해 도입이 저조했습니다. 더욱이, 이 컴파일러는 중간 O/S 파일이 생성되는 파라미터 plsql_native_library_dir를 정의할 것을 요구합니다.

Oracle Database 11g에서는, 서버에 C 컴파일러 혹은 파라미터 설정 없이도, 네이티브 컴파일이 가능합니다. 오직 필요한 것은 저장 코드 생성 혹은 재컴파일 전에 세션 파라미터를 설정하는 것입니다:

alter session set plsql_code_type = native;
... compile the code here ...
네이티브 컴파일은 번역 컴파일보다 오래 걸리지만, 이제 Oracle Database 11g에서는 프로세스가 훨씬 빨라, 그 결과는 크게 차이 나지 않을 수 있습니다. 일반 개발 시에는 번역 컴파일을, 개발 완료 시에는 네이티브 컴파일을 하는 것이 좋습니다.
11g 마이그레이션의 일환으로, 나는 미션 크리티컬 애플리케이션의 실제 코드를 가지고 실험하였습니다; 5,827 라인을 가진 꽤 긴 패키지이었습니다. 나는 기존 10g database와 새로운 11g에 서 각각 네이티브 컴파일을 한 후 번역 컴파일로 동일한 작업을 반복해 보았습니다. 이들 컴파일은 모두 plsql_optimize_level을 2로 설정하여 진행되었습니다. 각각의 컴파일 시간은 다음과 같이 나타났습니다 (단위: 초).

10g 11g
번역 1.66 1.64
네이티브 4.66 2.81

결과는 자명합니다. 번역 컴파일 시에는, 컴파일 시간의 차이는 거의 없습니다. 그러나, 네이티브 컴파일 시에는, 11g에서의 컴파일 시간이 10g의 약 60%로, 커다란 차이가 있습니다. 따라서, 11g 의 네이티브 컴파일을 컴파일 시간에 포함해도, 10g의 네이티브 컴파일 시간보다 빠릅니다

어떤 객체가 네이티브를 사용하여 컴파일 되었는지 알아보려면, 뷰 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가 모든PADDING-LEFT: 10px" align=right>

새로운 데이터 유형: 단순 인터저

네이티브 컴파일의 파워는 단순 인터저 (simple_integer)라 불리는 새로운 데이터 유형을 사용할 때 훨씬 강력합니다. 기술적으로 말하면, 이는 실제 데이터 유형이 아니라, 오히려 데이터 유형 pls_integer의 서브타입입니다. 단순 인터저는 소프트웨어가 아니라 하드웨어 산술 연산의 장점을 이용하기 위해 정의됩니다. 단순 인터저는 실제 네이티브 컴파일과 결합될 때, 성능이 획기적으로 개선됩니다. 다음 실험에서, 그 이유를 알게 될 것입니다.

simple_integer는 pls_integer의 서브타입이므로, 32비트 사인 인티저의 속성을 상속받고 -2,147,483,648과 2,147,483,647 사이의 정수 값을 가질 수 있습니다. 그러나, 이는 다음과 같이 pls_integer와 다릅니다: 이는 nulls을 허용하지 않고, 오버플로우를 허용합니다할당 값이 최대치를 넘어서면, 오류를 리턴하지 않고 랩어라운드(wrap around) 합니다.

이 데이터 유형은 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_integers의 또 하나 중요한 점은 최대 및 최소치를 넘어서는 값을 랩어라운드한다는 것입니다. 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_integers의 특성입니다. 이 같은 행태를 알고 있어야 합니다

단순 인티저로 실제 네이티브 컴파일 사용

알고 계시겠지만, simple_integers는 아무 곳에서나 사용할 수 없습니다; 이를 실행하기 전에, 경계 조건 (특히 값의 잠재적 랩핑)을 신중하게 결정해야 합니다. 또한, simple_integers는 네이티브 컴파일을 위해 디자인되었습니다. 번역 컴파일에서는, 이것들이 커다란 성능 향상을 이루지 못할 수 있습니다 (하지만, 나중에 알게 되겠지만, 해가 되는 것도 아닙니다). 실제 네이티브 모드에서, simple_integers의 성능 향상이 보다 확실합니다.

대부분의 PL/SQL 비즈니스 애플리케이션은 사실 SQL 기반이라, 이들 애플리케이션은 네이티브 컴파일을 통해 커다란 성능 향상을 보지 못할 것입니다. 예전에, 나는 수 천 개의 코드 라인으로 된 많은 수 및 통계 연산을 포함하는 PL/SQL를 사용해 데이터베이스 성능 계획 툴을 개발했습니다. 이것은 네이티브 컴파일을 사용했을 때, 엄청난 성능 향상을 이루었습니다. 단순 인터저는 당시 존재하지 않았지만, 그것이 있었다면, 보다 커다란 성능 향상이 있었을 것입니다.

인트라 유닛 인라이닝

인트라 유닛 인라이닝은 서브루틴 호출을 해당 서브루틴 코드 복사로 대체하는 것을 포함합니다. 일반적으로, 변경 코드는 보다 빨리 작동합니다. 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 컴파일러를 "최적화"해 코드의 제 2의 버전처럼 보이게 하면 어떨까요

Oracle Database 11g에서는 이것이 가능합니다. 필요한 것은 보다 높은 수준의 PL/SQL 최적화로 절차를 재컴파일하는 것입니다. 이는 2가지 방법으로 달성 가능합니다:

Set a session level parameter and recompile the procedure:
SQL> alter session set plsql_optimize_level = 3;

Session altered.
위의 설정은 PL/SQL 컴파일러가 코드를 인라인하기 위하여 코드를 재작성하도록 하기 위한 것입니다.

Compile the procedure with the plsql settings directly.
SQL> alter procedure upd_int
2 compile
3 plsql_optimize_level = 3
4 reuse settings;

Procedure altered.
동일 세션에서 컴파일된 다른 모든 절차는 영향을 받지 않습니다. 이는 동일 세션에서 컴파일할 절차가 많을 때 인라이닝을 처리하는 보다 좋은 방법입니다.컴파일러 명령인 pragma를 사용할 수도 있습니다.
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')을 추가했습니다. 마찬가지로, plsql_optimizer_level이 3으로 설정되었다 하더라도, 컴파일러가 이 절차를 인라이닝하지 못하도록 “NO”라고 할 수도 있습니다.
이 인라이닝은 코드가 보다 빠르게 실행되도록 합니다. 물론, 효과 정도는 컴파일러에 의해 어느 정도의 인라이닝이 가능한가에 따라 달라집니다. 이 문서의 마지막 부분에서, 인라이닝이 사용된 코드의 사례를 보면, 성능 향상을 이해할 수 있게 될 것입니다.

컴파일 시간

물론, 최적화 프로세스는 컴파일러의 작업 부하를 늘립니다. 어느 정도 늘릴까요

이에 답하기 위해서, 사전 제공된, 인라인/비 인라인 및 번역/네이티브의 서로 다른 조합으로 컴파일된 실제 애플리케이션 코드를 사용했습니다. 결과는 다음과 같습니다:

인라인 비 인라인
번역 1.70 1.64
네이티브 3.15 2.81

결과는 자명합니다. 인라인 옵션을 사용해 컴파일하면, 컴파일 시간은 번역일 때 단지 소폭 (약 4%) 증가합니다. 네이티브 컴파일 시엔, 훨씬 큰 폭 (약 12%)으로 증가합니다. 따라서, 개발 주기에서는 애플리케이션을 인라인/번역 옵션을 사용하여 컴파일 하고, 최종 단계에서 네이티브 컴파일하는 것이 좋습니다. 인라이닝의 사용이 컴파일 프로세스에 많은 시간을 추가 소요하는 것 같지 않으며, 따라서 개발 주기에 별 영향을 미치지 않을 수 있습니다.
이제 중요한 문제가 남아 있습니다: 코드를 변경하지 않고, 어떻게 코드가 인라이닝되었는지 확인할 수 있을까요 세션 변수를 설정하면 가능합니다:

alter session set plsql_warnings = 'enable:all';
여기서 절차를 재생성합니다:
SQL> @upd_int

SP2-0804: Procedure created with compilation warnings
여기서 경고가 발령됩니다. 경고를 보려면, 다음 커맨드를 사용하십시오: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
절차 calc_int가 실제로 인라이닝되었음을 확인시켜주는 마지막 라인을 주목하십시오.
어떤 객체가 어는 레벨로 컴파일되었는지 알려면, 뷰 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
... and so on ...
모든 객체를 위한 유사한 뷰가 존재합니다: DBA_PLSQL_OBJECT_SETTINGS.
이는 인트라 유닛 인라이닝으로, 유닛 내부의 절차만 인라이닝 된다는 것을 기억하십시오. 유닛 외부의 서브루틴은 인라이닝되지 않습니다.

실험 재현

이제 실험 재현을 통해 이들 장점을 확인해 볼 차례입니다. 여기서 패키지의 기본 버전을 생성한 다음, 변수 데이터 유형과 컴파일 명령을 앞서 배운 개념에 따라 변경할 것입니다.

우선, 유클리드 알고리즘을 실행해 두 수의 최대 공약수를 찾아냅니다. 이것이 페이지의 함수 로직입니다:

function gcd(a, b)
if a = 0 return b
while b ≠ 0
if a > b
a := a - b
else
b := b - a
return a
이들 변경자의 다양한 변수 조합에 따라 패키지 실행에 소요되는 CPU 시간을 측정하고 이를 기록합니다. 그런 다음, CPU 시간을 저장할 테이블을 생성합니다:
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 등 3가지 칼럼이 있습니다. 칼럼의 "Y"는 이를 위해 컴파일된 코드를 의미합니다. 따라서, 레코드는 다음과 같습니다
NATIVE SIMPLE INLINING CENTISECONDS
------ ------ -------- ------------
Y N N 100
이는 프로그램이 네이티브 컴파일되었으나 단순 인티저는 사용되지 않았고, 인라이닝은 발생하지 않았으며, 이 조합이 100 센티초의 CPU 시간이 걸렸다는 것을 나타냅니다
패키지의 단일 복사본을 사용하려면, 조건부 컴파일 (Oracle Database 10g Release 2에서 소개됨)을 사용해 패키지를 변경합니다. 패키지 생성 방법은 다음과 같습니다:

-- suppress these expected warnings:
-- 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
-- expected value depends on 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()는 2개의 값을 승인하고 그들 중 최대 공약수를 리턴합니다. 함수 exercise_gcd()는 GCD() 함수를 호출하고 센티초 단위의 CPU 실행 시간을 리턴합니다. 마지막으로 퍼블릭드를 TIMES 테이블에 삽입힙니다.
이제 서로 다른 변경자를 사용해 몇 차례 패키지 절차를 호출하고 각각의 CPU 시간을 기록합니다. 패키지를 컴파일하기 전에, 조건부 컴파일 변수를 변경해 이를 수행합니다:

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:true
-- no 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:false
-- no 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:true
-- no 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
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

결과는 다음과 같습니다. 이는 디폴트를 기준으로 할 때 인라이닝 아님, 번역 컴파일 및 pls_integer 사용 시 CPU 시간 비율을 나타냅니다

------------------------------------------------------------Benefit of simple_integerInterpreted and no inlining 1.00
Interpreted and inlining 1.00
Native and no inlining 2.19
Native and inlining 2.79

------------------------------------------------------------Benefit of inliningInterpreted and pls_integer 1.07
Interpreted and simple_integer 1.07
Native and pls_integer 1.16
Native and simple_integer 1.48

------------------------------------------------------------Benefit of nativepls_integer and no inlining 4.78
pls_integer and inlining 5.18
simple_integer and no inlining 10.53
simple_integer and inlining 14.49위의 결과로부터, 인라이닝 및 simple_integer를 사용한 네이티브 컴파일과 비교해, 디폴트로 설정된 CPU 실행 시간이 14.49배이며, 다른 어떤 기준에 비해서도 상당히 높다는 것을 알 수 있습니다.

결론

이제 이들 기능의 파워 및 유용성을 평가해야 합니다. 결론은 다음과 같습니다:

simple_integer 데이터 유형은 pls_integer가 값 랩핑될 가능성 및 null이 아니어야 한다는 점만 유의하면, 어디서나 구문론적으로 사용될 수 있습니다. 이는 단순 인티저가 모든 곳에서 아무렇게나 사용될 수 있는 것은 아니라는 것을 의미합니다. 애플리케이션이 최대 값을 초과하거나 최소 값을 하회할 가능성이 없으면, 이를 네이티브 컴파일과 결합해 사용해 매우 좋은 성과를 낼 수 있습니다.

simple_integer의 효과는 네이티브 컴파일과 함께 사용될 때 가장 좋고, 번역 컴파일과 함께 사용할 때 미약합니다. 번역 컴파일과 함께 사용하더라도, simple_integer는 해가 되지 않으며 이득이 됩니다.

인라이닝의 효과 또한 번역 컴파일과 함께 사용할 때보다 네이티브 컴파일과 함께 사용될 때 훨씬 큽니다. 아는 설명하기 어렵습니다. 개략적으로 말하면, 공통 컴파일의 백엔드와 관련된 코드가 조밀할수록, 번역을 사용할 수 없는 영역에서, 네이티브가 최적화될 수 있는 가능성이 큽니다

네이티브의 효과는 프로그램이 수용적일 때, 즉, SQL과 오라클 넘버 및 날짜 등을 사용하지 않을 때 가장 큽니다. 그러나 이들 조건이 충족되더하십시오.

Oracle Database 11g에서 네이티브 컴파일 및 인라이닝을 사용하는 것은 매우 쉽습니다. 이를 사용하지 않는 유일한 이유는 (개발 단계의 초기에) 컴파일 시간이 많이 걸린다는 것일 수 있습니다

simple_integer를 안전하게 사용할 수 있는 기회는 많지 않을 것입니다. 그러나 기회가 생기면, 잡아야 합니다.