I know – everyone knows, that PL/SQL packages are faster than stored procedures. If you’ll ask anyone at the training or in your dev team "what is better" – you’ll (almost) always hear: PL/SQL packages. But why exactly?
The documentation says:
The first time you invoke a package subprogram, Oracle Database loads the whole package into memory. Subsequent invocations of other subprograms in same the package require no disk I/O.
Source: http://docs.oracle.com/database/121/LNPLS/packages.htm#LNPLS00904
OK, it’s understandable and feels natural. But let’s try to think about it for a while.
I’ve created one procedure, one function and a package, containing those two. As simple as it can be:
SQL> create or replace function by2(x number) return number as 2 begin 3 return x*2; 4 end; 5 / Function created. SQL> create or replace procedure proc_by2 as 2 x number; 3 begin 4 for i in 1..1000000 loop 5 x:=by2(i); 6 end loop; 7 end; 8 / Procedure created. SQL> create or replace package pkg_test_calls as 2 function by2(x number) return number; 3 procedure by2_proc; 4 end; 5 / Package created. SQL> ed Wrote file afiedt.buf 1 create or replace package body pkg_test_calls as 2 function by2(x number) return number is 3 begin 4 return x*2; 5 end by2; 6 procedure by2_proc is 7 vx number; 8 begin 9 for i in 1..1000000 loop 10 vx:=by2(i); 11 end loop; 12 end; 13* end; 14 / Package body created.
I think that for this simple example, using "set timing on" will be sufficient enough.
SQL> set timing on SQL> exec proc_by2 PL/SQL procedure successfully completed. Elapsed: 00:00:00.25 SQL> exec pkg_test_calls.by2_proc PL/SQL procedure successfully completed. Elapsed: 00:00:00.16 SQL> exec proc_by2 PL/SQL procedure successfully completed. Elapsed: 00:00:00.23 SQL> exec pkg_test_calls.by2_proc PL/SQL procedure successfully completed. Elapsed: 00:00:00.16 SQL>
So why there is a difference here? I don’t see any place here for disk I/O. Both procedures were compiled and loaded into memory, so we can’t speak about subsequent loads.
To understand what has happened I’ve created a very simple DTrace script, which will count the number of function calls, made by server process, associated with my session. I’ve used DTrace ability to trace functions, based on PID provider.
This technique is called "User-space Probing" and can be done also on Linux, using dynamic tracing tools like Systemtap https://sourceware.org/systemtap/SystemTap_Beginners_Guide/userspace-probing.html
root@skiper:~# cat function_calls.d #!/usr/sbin/dtrace -s pid$1:::entry /pid==$1/ { @fcount[probemod,probefunc]=count(); }
Let’s check the tail output for test with package and separate PL/SQL stored function/procedure.
root@skiper:~# tail -20 fcalls_proc_fun.out oracle lnxmin 1000000 oracle lnxmin_int 1000000 oracle lnxmul 1000000 oracle pfrinstr_BCTR 1000000 oracle pfrinstr_CVTIN 1000000 oracle pfrinstr_MULN 1000000 oracle sslnxmul 1000000 oracle kgiPinObject 1000002 oracle pevm_ENTER 1000002 oracle pevm_RET 1000002 oracle pfrinstr_RET 1000002 oracle pfrust 1000002 oracle pfrxca 1000002 oracle pevm_CHK_INST 1000003 oracle pfrinstr_XCAL 1000003 oracle pfrinstr_XCAL_SCAL 1000003 oracle kglPinSetContext 1000004 oracle kglpnp 1000006 oracle kss_get_owner 1000012 oracle kss_get_type 1000028 root@skiper:~# tail -20 fcalls_pkg.out oracle pdz2M20_Audit_In_No_PDZ0_DEBUG 634 oracle dbgtrPrepareWrite 635 oracle pdz9M08_Type_Kind 689 libc.so.1 setjmp 717 oracle kghuwrlength 755 oracle pdzgM07_Get 774 oracle pdz7M12_Opcode 792 libc.so.1 memset 1200 oracle lnxmul 1000000 oracle pfrinstr_BCTR 1000000 oracle pfrinstr_CALL 1000000 oracle pfrinstr_CVTIN 1000000 oracle pfrinstr_MULN 1000000 oracle sslnxmul 1000000 oracle lnxmin 1000001 oracle lnxmin_int 1000001 oracle pevm_ENTER 1000002 oracle pevm_RET 1000002 oracle pfrinstr_RET 1000002 oracle pfrust 1000002
We don’t need a very detail investigation to notice the difference between those two executions.
I decided to check the execution times of 4 particular functions:
- plsql_run
- kglpnp
- kglPinSetContext
- kgiPinObject
The first one is responsible for the whole PL/SQL initialization and execution, so its time should be similar to what we can see in SQL*Plus timing. The next two should have something to do with maintaining the PL/SQL objects in library cache – "oradebug doc component" shows, that "kgl" components are responsible for LIBRARY CACHE:
ora-600:~ inter$ grep -i kgl ORA-600/scripts/oracle/components.doc LIBCACHE LIBCACHE (kgl, kql)
I’m guessing by the name, that the last one should be responsible for pining the PL/SQL object in library cache for the execution time.
Based on this I used a very simple DTrace script to check the execution time and number of executions of each function:
root@skiper:~# cat plsql_proc_fun_time.d #!/usr/sbin/dtrace -s #pragma D option quiet uint64_t kpoTime; uint64_t plsqlrunTime; uint64_t kglpnpTime; uint64_t contextTime; BEGIN { printf("%s\n","START PROFILING kgiPinObject, plsql_run, kglpnpi,kglPinSetContext execution times"); } pid$1::kgiPinObject:entry /pid==$1/ { self->t=timestamp; } pid$1::kgiPinObject:return /pid==$1 && self->t>0/ { kpoTime+=timestamp-self->t; self->t=0; @stackKgi[ustack()]=count(); kgiPinObjectExec++; } pid$1::plsql_run:entry /pid==$1/ { self->pt=timestamp; } pid$1::plsql_run:return /pid==$1 && self->pt>0/ { plsqlrunTime+=timestamp-self->pt; self->pt=0; plsql_runExec++; } pid$1::kglpnp:entry /pid==$1/ { self->kt=timestamp; } pid$1::kglpnp:return /pid==$1 && self->kt>0/ { kglpnpTime+=timestamp-self->kt; self->kt=0; @stackKgl[ustack()]=count(); kglpnpExec++; } pid$1::kglPinSetContext:entry /pid==$1/ { self->ct=timestamp; } pid$1::kglPinSetContext:return /pid==$1 && self->ct>0/ { contextTime+=timestamp-self->ct; self->ct=0; @stackCtx[ustack()]=count(); kglPinSetContextExec++; } END { printa(@stackKgi); printa(@stackKgl); printa(@stackCtx); printf("kgiPinObject executed %d times, waited in ms: %d\n",kgiPinObjectExec,kpoTime/1000000); printf("kglpnp executed %d times, waited in ms: %d\n",kglpnpExec, kglpnpTime/1000000); printf("kglPinSetContext executed %d times, waited in ms: %d\n",kglPinSetContextExec, contextTime/1000000); printf("plsql_run executed %d times, waited in ms: %d\n",plsql_runExec, plsqlrunTime/1000000); sm=kpoTime+kglpnpTime+contextTime; printf("kgiPinObject+kglpnp+kglPinSetContext = %d ms \n",sm/1000000); }
Please notice, that this script will seriously affect the execution times, because monitoring can change the monitored object 😉
So right now the execution time for package looks like this:
SQL> exec pkg_test_calls.by2_proc PL/SQL procedure successfully completed. Elapsed: 00:00:00.18 SQL>
And this is the output of my Dtrace script:
root@skiper:~# ./plsql_proc_fun_time.d 3450 START PROFILING kgiPinObject, plsql_run, kglpnpi,kglPinSetContext execution times ^C oracle`kgiPinObject+0xb8 oracle`pfrinstr_XCAL_SCAL+0x87 oracle`pfrrun_no_tool+0x12a oracle`pfrrun+0x4c0 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 oracle`0x6e80c2b 1 oracle`kglpnp+0x140 oracle`pfri7_inst_body_common+0x88 oracle`pfri3_inst_body+0x43 oracle`pfrrun+0x685 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 oracle`0x6e80c2b 1 oracle`kglpnp+0x140 oracle`pfri8_inst_spec+0x83 oracle`pfri1_inst_spec+0x43 oracle`pfrrun+0x5e2 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 oracle`0x6e80c2b 1 oracle`kglpnp+0x140 oracle`pevm_CHK_INST+0x49 oracle`pfrinstr_XCAL_SCAL+0x87 oracle`pfrrun_no_tool+0x12a oracle`pfrrun+0x4c0 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 1 oracle`kglpnp+0x140 oracle`pfri7_inst_body_common+0x118 oracle`pfri3_inst_body+0x43 oracle`pfrrun+0x685 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 oracle`0x6e80c2b 1 oracle`kglPinSetContext+0x76 oracle`pevm_CHK_INST+0x49 oracle`pfrinstr_XCAL_SCAL+0x87 oracle`pfrrun_no_tool+0x12a oracle`pfrrun+0x4c0 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 1 oracle`kglPinSetContext+0x76 oracle`kgiind+0x431 oracle`pfri8_inst_spec+0x83 oracle`pfri1_inst_spec+0x43 oracle`pfrrun+0x5e2 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 1 oracle`kglPinSetContext+0x76 oracle`kglpndl+0xd23 oracle`kss_del_cb+0x19d oracle`kssdel+0xf2 oracle`kssdch_int+0x326 oracle`ksudlc+0xf6 oracle`kss_del_cb+0x106 oracle`kssdel+0xf2 oracle`ksupop+0x23a oracle`opiodr+0x4b1 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 oracle`0x6e80c2b 2 kgiPinObject executed 1 times, waited in ms: 0 kglpnp executed 4 times, waited in ms: 0 kglPinSetContext executed 4 times, waited in ms: 0 plsql_run executed 1 times, waited in ms: 172 kgiPinObject+kglpnp+kglPinSetContext = 0 ms
And this is the execution time of procedure call:
SQL> exec proc_by2 PL/SQL procedure successfully completed. Elapsed: 00:00:24.38
And the result of my DTrace script for this execution:
root@skiper:~# ./plsql_proc_fun_time.d 3450 START PROFILING kgiPinObject, plsql_run, kglpnpi,kglPinSetContext execution times ^C oracle`kgiPinObject+0xb8 oracle`pfrinstr_XCAL_SCAL+0x87 oracle`pfrrun_no_tool+0x12a oracle`pfrrun+0x4c0 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 oracle`0x6e80c2b 1000002 oracle`kglpnp+0x140 oracle`pfri7_inst_body_common+0x88 oracle`pfri3_inst_body+0x43 oracle`pfrrun+0x685 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 oracle`0x6e80c2b 2 oracle`kglpnp+0x140 oracle`pfri8_inst_spec+0x83 oracle`pfri1_inst_spec+0x43 oracle`pfrrun+0x5e2 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 oracle`0x6e80c2b 2 oracle`kglpnp+0x140 oracle`pevm_CHK_INST+0x49 oracle`pfrinstr_XCAL_SCAL+0x87 oracle`pfrrun_no_tool+0x12a oracle`pfrrun+0x4c0 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 1000002 oracle`kglPinSetContext+0x76 oracle`kgiind+0x431 oracle`pfri8_inst_spec+0x83 oracle`pfri1_inst_spec+0x43 oracle`pfrrun+0x5e2 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 1 oracle`kglPinSetContext+0x76 oracle`kglpndl+0xd23 oracle`kss_del_cb+0x19d oracle`kssdel+0xf2 oracle`kssdch_int+0x326 oracle`ksudlc+0xf6 oracle`kss_del_cb+0x106 oracle`kssdel+0xf2 oracle`ksupop+0x23a oracle`opiodr+0x4b1 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 oracle`0x6e80c2b 2 oracle`kglPinSetContext+0x76 oracle`pevm_CHK_INST+0x49 oracle`pfrinstr_XCAL_SCAL+0x87 oracle`pfrrun_no_tool+0x12a oracle`pfrrun+0x4c0 oracle`plsql_run+0x288 oracle`peicnt+0x15f oracle`kkxexe+0x2f3 oracle`opiexe+0x32c3 oracle`kpoal8+0xa3d oracle`opiodr+0x433 oracle`ttcpip+0x593 oracle`opitsk+0x6cc oracle`opiino+0x3c5 oracle`opiodr+0x433 oracle`opidrv+0x33b oracle`sou2o+0x7a oracle`opimai_real+0x265 oracle`ssthrdmain+0x22e oracle`main+0xa4 1000001 kgiPinObject executed 1000002 times, waited in ms: 19482 kglpnp executed 1000006 times, waited in ms: 3532 kglPinSetContext executed 1000004 times, waited in ms: 3676 plsql_run executed 1 times, waited in ms: 24380 kgiPinObject+kglpnp+kglPinSetContext = 26692 ms
As you can see, the numbers are accurate enough to make some conclusions: execution time of function kgiPinObject is almost 80% of the whole execution time!
So the reason why PL/SQL packages are faster in subsequent invocations of other subprograms is not the "disk I/O", but penalty due to the need to pin/lock the foreign object in Library Cache each time, when I want to execute it.
So the practical knowledge "the lightning kills" did not change, but now at least I know, that it’s not because Zeus is mad at me 😉
P.S.
If you want to meet true experts in PL/SQL, you have to visit Poland, 7/8 OCTOBER 2016 at POUG (Pint with Oracle Users Group). Details can be found in here: http://poug.org/en/
Name of the function, which executes kgiPinObject will give -10% for the ticket 😉