Minimizing CPU Overhead
Topics
Tune SQL Statements
The most common cause of slowness in PL/SQL programs is slow SQL statements. To make SQL statements in a PL/SQL program as efficient as possible:
-
Use appropriate indexes.
For details, see Oracle Database Performance Tuning Guide.
-
Use query hints to avoid unnecessary full-table scans.
For details, see Oracle Database SQL Language Reference.
-
Collect current statistics on all tables, using the subprograms in the
DBMS_STATS
package.For details, see Oracle Database Performance Tuning Guide.
-
Analyze the execution plans and performance of the SQL statements, using:
-
EXPLAIN
PLAN
statementFor details, see Oracle Database Performance Tuning Guide.
-
SQL Trace facility with
TKPROF
utilityFor details, see Oracle Database Performance Tuning Guide.
-
-
Use bulk SQL, a set of PL/SQL features that minimizes the performance overhead of the communication between PL/SQL and SQL.
For details, see "Bulk SQL and Bulk Binding".
Tune Function Invocations in Queries
Functions invoked in queries might run millions of times. Do not invoke a function in a query unnecessarily, and make the invocation as efficient as possible.
Create a function-based index on the table in the query. The CREATE
INDEX
statement might take a while, but the query can run much faster because the function value for each row is cached.
If the query passes a column to a function, then the query cannot use user-created indexes on that column, so the query might invoke the function for every row of the table (which might be very large). To minimize the number of function invocations, use a nested query. Have the inner query filter the result set to a small number of rows, and have the outer query invoke the function for only those rows.
See Also:
-
Oracle Database SQL Language Reference for more information about
CREATE
INDEX
statement syntax -
"PL/SQL Function Result Cache" for information about caching the results of PL/SQL functions
Example 13-5 Nested Query Improves Performance
In this example, the two queries produce the same result set, but the second query is more efficient than the first. (In the example, the times and time difference are very small, because the EMPLOYEES
table is very small. For a very large table, they would be significant.)
DECLARE starting_time TIMESTAMP WITH TIME ZONE; ending_time TIMESTAMP WITH TIME ZONE; BEGIN -- Invokes SQRT for every row of employees table: SELECT SYSTIMESTAMP INTO starting_time FROM DUAL; FOR item IN ( SELECT DISTINCT(SQRT(department_id)) col_alias FROM employees ORDER BY col_alias ) LOOP DBMS_OUTPUT.PUT_LINE('Square root of dept. ID = ' || item.col_alias); END LOOP; SELECT SYSTIMESTAMP INTO ending_time FROM DUAL; DBMS_OUTPUT.PUT_LINE('Time = ' || TO_CHAR(ending_time - starting_time)); -- Invokes SQRT for every distinct department_id of employees table: SELECT SYSTIMESTAMP INTO starting_time FROM DUAL; FOR item IN ( SELECT SQRT(department_id) col_alias FROM (SELECT DISTINCT department_id FROM employees) ORDER BY col_alias ) LOOP IF item.col_alias IS NOT NULL THEN DBMS_OUTPUT.PUT_LINE('Square root of dept. ID = ' || item.col_alias); END IF; END LOOP; SELECT SYSTIMESTAMP INTO ending_time FROM DUAL; DBMS_OUTPUT.PUT_LINE('Time = ' || TO_CHAR(ending_time - starting_time)); END; /
Result is similar to:
Square root of dept. ID = 3.16227766016837933199889354443271853372 Square root of dept. ID = 4.47213595499957939281834733746255247088 Square root of dept. ID = 5.47722557505166113456969782800802133953 Square root of dept. ID = 6.32455532033675866399778708886543706744 Square root of dept. ID = 7.07106781186547524400844362104849039285 Square root of dept. ID = 7.74596669241483377035853079956479922167 Square root of dept. ID = 8.36660026534075547978172025785187489393 Square root of dept. ID = 8.94427190999915878563669467492510494176 Square root of dept. ID = 9.48683298050513799599668063329815560116 Square root of dept. ID = 10 Square root of dept. ID = 10.48808848170151546991453513679937598475 Time = +000000000 00:00:00.046000000 Square root of dept. ID = 3.16227766016837933199889354443271853372 Square root of dept. ID = 4.47213595499957939281834733746255247088 Square root of dept. ID = 5.47722557505166113456969782800802133953 Square root of dept. ID = 6.32455532033675866399778708886543706744 Square root of dept. ID = 7.07106781186547524400844362104849039285 Square root of dept. ID = 7.74596669241483377035853079956479922167 Square root of dept. ID = 8.36660026534075547978172025785187489393 Square root of dept. ID = 8.94427190999915878563669467492510494176 Square root of dept. ID = 9.48683298050513799599668063329815560116 Square root of dept. ID = 10 Square root of dept. ID = 10.48808848170151546991453513679937598475 Time = +000000000 00:00:00.000000000
Tune Subprogram Invocations
If a subprogram has OUT
or IN
OUT
parameters, you can sometimes decrease its invocation overhead by declaring those parameters with the NOCOPY
hint.
When OUT
or IN
OUT
parameters represent large data structures such as collections, records, and instances of ADTs, copying them slows execution and increases memory use—especially for an instance of an ADT.
For each invocation of an ADT method, PL/SQL copies every attribute of the ADT. If the method is exited normally, then PL/SQL applies any changes that the method made to the attributes. If the method is exited with an unhandled exception, then PL/SQL does not change the attributes.
If your program does not require that an OUT
or IN
OUT
parameter retain its pre-invocation value if the subprogram ends with an unhandled exception, then include the NOCOPY
hint in the parameter declaration. The NOCOPY
hint requests (but does not ensure) that the compiler pass the corresponding actual parameter by reference instead of value.
Caution:
Do not rely on NOCOPY
(which the compiler might or might not obey for a particular invocation) to ensure that an actual parameter or ADT attribute retains its pre-invocation value if the subprogram is exited with an unhandled exception. Instead, ensure that the subprogram handle all exceptions.
See Also:
-
"NOCOPY" for more information about
NOCOPY
hint -
Oracle Database Object-Relational Developer's Guide for information about using
NOCOPY
with member methods of ADTs
Example 13-6 NOCOPY Subprogram Parameters
In this example, if the compiler obeys the NOCOPY
hint for the invocation of do_nothing2
, then the invocation of do_nothing2
is faster than the invocation of do_nothing1
.
DECLARE TYPE EmpTabTyp IS TABLE OF employees%ROWTYPE; emp_tab EmpTabTyp := EmpTabTyp(NULL); -- initialize t1 NUMBER; t2 NUMBER; t3 NUMBER; PROCEDURE get_time (t OUT NUMBER) IS BEGIN t := DBMS_UTILITY.get_time; END; PROCEDURE do_nothing1 (tab IN OUT EmpTabTyp) IS BEGIN NULL; END; PROCEDURE do_nothing2 (tab IN OUT NOCOPY EmpTabTyp) IS BEGIN NULL; END; BEGIN SELECT * INTO emp_tab(1) FROM employees WHERE employee_id = 100; emp_tab.EXTEND(49999, 1); -- Copy element 1 into 2..50000 get_time(t1); do_nothing1(emp_tab); -- Pass IN OUT parameter get_time(t2); do_nothing2(emp_tab); -- Pass IN OUT NOCOPY parameter get_time(t3); DBMS_OUTPUT.PUT_LINE ('Call Duration (secs)'); DBMS_OUTPUT.PUT_LINE ('--------------------'); DBMS_OUTPUT.PUT_LINE ('Just IN OUT: ' || TO_CHAR((t2 - t1)/100.0)); DBMS_OUTPUT.PUT_LINE ('With NOCOPY: ' || TO_CHAR((t3 - t2))/100.0); END; /
Tune Loops
Because PL/SQL applications are often built around loops, it is important to optimize both the loops themselves and the code inside them.
If you must loop through a result set more than once, or issue other queries as you loop through a result set, you might be able to change the original query to give you exactly the results you want. Explore the SQL set operators that let you combine multiple queries, described in Oracle Database SQL Language Reference.
You can also use subqueries to do the filtering and sorting in multiple stages—see "Processing Query Result Sets with Subqueries".
See Also:
Tune Computation-Intensive PL/SQL Code
These recommendations apply especially (but not only) to computation-intensive PL/SQL code.
Topics
Use Data Types that Use Hardware Arithmetic
Avoid using data types in the NUMBER
data type family (described in "NUMBER Data Type Family"). These data types are represented internally in a format designed for portability and arbitrary scale and precision, not for performance. Operations on data of these types use library arithmetic, while operations on data of the types PLS_INTEGER
, BINARY_FLOAT
and BINARY_DOUBLE
use hardware arithmetic.
For local integer variables, use PLS_INTEGER
, described in "PLS_INTEGER and BINARY_INTEGER Data Types". For variables used in performance-critical code, that can never have the value NULL
, and do not need overflow checking, use SIMPLE_INTEGER
, described in "SIMPLE_INTEGER Subtype of PLS_INTEGER".
For floating-point variables, use BINARY_FLOAT
or BINARY_DOUBLE
, described in Oracle Database SQL Language Reference. For variables used in performance-critical code, that can never have the value NULL
, and that do not need overflow checking, use SIMPLE_FLOAT
or SIMPLE_DOUBLE
, explained in "Additional PL/SQL Subtypes of BINARY_FLOAT and BINARY_DOUBLE".
Note:
BINARY_FLOAT
and BINARY_DOUBLE
and their subtypes are less suitable for financial code where accuracy is critical, because they do not always represent fractional values precisely, and handle rounding differently than the NUMBER
types.
Many SQL numeric functions (described in Oracle Database SQL Language Reference) are overloaded with versions that accept BINARY_FLOAT
and BINARY_DOUBLE
parameters. You can speed up computation-intensive code by passing variables of these data types to such functions, and by invoking the conversion functions TO_BINARY_FLOAT
(described in Oracle Database SQL Language Reference) and TO_BINARY_DOUBLE
(described in Oracle Database SQL Language Reference) when passing expressions to such functions.
Avoid Constrained Subtypes in Performance-Critical Code
In performance-critical code, avoid constrained subtypes (described in "Constrained Subtypes"). Each assignment to a variable or parameter of a constrained subtype requires extra checking at run time to ensure that the value to be assigned does not violate the constraint.
See Also:
PL/SQL Predefined Data Types includes predefined constrained subtypes
Minimize Implicit Data Type Conversion
At run time, PL/SQL converts between different data types implicitly (automatically) if necessary. For example, if you assign a PLS_INTEGER
variable to a NUMBER
variable, then PL/SQL converts the PLS_INTEGER
value to a NUMBER
value (because the internal representations of the values differ).
Whenever possible, minimize implicit conversions. For example:
-
If a variable is to be either inserted into a table column or assigned a value from a table column, then give the variable the same data type as the table column.
Tip:
Declare the variable with the
%TYPE
attribute, described in "%TYPE Attribute". -
Make each literal the same data type as the variable to which it is assigned or the expression in which it appears.
-
Convert values from SQL data types to PL/SQL data types and then use the converted values in expressions.
For example, convert
NUMBER
values toPLS_INTEGER
values and then use thePLS_INTEGER
values in expressions.PLS_INTEGER
operations use hardware arithmetic, so they are faster thanNUMBER
operations, which use library arithmetic. For more information about thePLS_INTEGER
data type, see "PLS_INTEGER and BINARY_INTEGER Data Types". -
Before assigning a value of one SQL data type to a variable of another SQL data type, explicitly convert the source value to the target data type, using a SQL conversion function (for information about SQL conversion functions, see Oracle Database SQL Language Reference).
-
Overload your subprograms with versions that accept parameters of different data types and optimize each version for its parameter types. For information about overloaded subprograms, see "Overloaded Subprograms".
See Also:
-
Oracle Database SQL Language Reference for information about implicit conversion of SQL data types (which are also PL/SQL data types)
Use SQL Character Functions
SQL has many highly optimized character functions, which use low-level code that is more efficient than PL/SQL code. Use these functions instead of writing PL/SQL code to do the same things.
See:
-
Oracle Database SQL Language Reference for information about SQL character functions that return character values
-
Oracle Database SQL Language Reference for information about SQL character functions that return NLS character values
-
Oracle Database SQL Language Reference for information about SQL character functions that return number values
-
Example 7-6 for an example of PL/SQL code that uses SQL character function
REGEXP_LIKE
Put Least Expensive Conditional Tests First
PL/SQL stops evaluating a logical expression as soon as it can determine the result. Take advantage of this short-circuit evaluation by putting the conditions that are least expensive to evaluate first in logical expressions whenever possible. For example, test the values of PL/SQL variables before testing function return values, so that if the variable tests fail, PL/SQL need not invoke the functions:
IF boolean_variable OR (number > 10) OR boolean_function(parameter) THEN ...
See Also: