The following list contains some general performance-related tips and recommendations of the PL/I language.
- Avoid the use of LABEL variables.
- Do not use the GOTO statement except to transfer control out of an ON unit. Use LEAVE or RETURN instead.
- For never-ending loops, use DO LOOP (or DO FOREVER), and LEAVE to exit the do loop when exit conditions are met.
- For flags, use BIT(1) rather than fields of other types, and combine related flags into the same byte using a structure.
Testing such combinations of flags using and/or (&/|) can result in fewer and faster instructions.
- When testing a non-BIT variable, it is best to include both operands; for example,
IF index ^= 0, and not
IF index.
- Avoid implicit comparisons; for example, assuming all operands are BIT(1), it is better to use
b1 = ''b; IF b2 = b3 then b1 = '1'b; rather than
b1 = b2 = b3;, which is effectively
IF b2 = b3 then b1 = '1'b; else b1 = '0'b;
- If possible, it is recommended to order statements/expressions in order of probability, specifying the most likely comparisons
(of being true or false) before others that are less likely.
- Make use of the REPEAT, UNTIL, WHILE syntax to express program logic: the Compiler will ensure these are processed in the
most efficient manner.
- The SELECT...WHEN...OTHERWISE statement group leads to better program readability and maintainability, and can generate more
efficient code than nested IF statements. When using a SELECT group, it is good practice to code an OTHERWISE statement; when
one is not coded, the Compiler effectively inserts an OTHERWISE SIGNAL ERROR; statement.
- Do not use excessive amounts of modularization by creating lots of tiny procedures.
- Avoid BEGIN statements unless they are the body of an ON-unit, or you need to dynamically allocate an unusual amount of automatic (and possibly only dynamically/demand-needed) storage.
- Compound statements allow the Compiler to 'see' the target and generally allow for better code generation, particularly for
operations such as concatenate (||) that require potentially large temporaries; for example;
-
Target += expression; is better than
Target = Target + expression;
- Target ||= expression; is better than
Target = Target || expression;, but works only if
Target is VARYING.
- Do not take advantage of the Compiler closing multiple groups (DO, SELECT) or blocks (BEGIN, PROCEDURE) with a single END
statement. It will very likely be incorrect and may impact performance.
- Multiple entry points (ENTRY statements) in a procedure my result in extra instructions to remap parameters and/or tests
to determine use of parameters. If the procedure is a function, it is better to have all entry points be functions, and have
them return the same data type. Differing return types can cause conversion code to be generated under the control of a possibly
complex Compiler-generated SELECT group.
- If you are using the FETCH statement, the following:
DCL fe entry[( …)] options(fetchable […]);
DCL fev entry variable;
fetch fe;
fev = fe;
Call fev [ (….) ]; /* note fev entry variable is being called here */
is more efficient than:
DCL fe entry[( …)] options(fetchable […]);
Call fe [ (….) ]; /* note fe entry constant is being called here */
If appropriate, it may be beneficial to RELEASE a fetched entry when it is no longer needed.
- When using dynamically allocated storage, if the application logic allows it, it can be more efficient to ALLOCATE storage
when needed and FREE it when done. Also, if the application logic permits, it is much more efficient to use a BEGIN block
for the life of the storage. AUTOMATIC storage is allocated with just a few instructions and freed with even fewer; for example;
DCL some_storage char(some_len) based(stg_ptr),
Some_len = expression;
ALLOCATE some_storage;
[ … storage use .. ]
FREE some-storage;
can be replaced by this much more efficient code:
BEGIN;
DCL some_storage char(some_len) [AUTO];
[ … storage use .. ]
END;