The performance of REXX programs can be improved by using the following hints.
These hints can lead to significant performance improvements when instructions within loops are reduced. The use of these hints can negatively impact program readability. So, if performance is not a significant concern, or the instructions are performed outside loops, you may prefer to use the more readable versions instead. |
There is a bit of overhead associated with each REXX instruction. Consequently, a performance improvement can be attained by reducing one, or more, instructions to a single instruction, as in:
if condition then instruction |
To:
other-instruction using conditional phrase |
For example, the COPIES built-in function can be used to optionally add a text segment to a string. When the second argument is '1' the segment is added, but not if the second argument is a '0'. So the following instructions:
if wordpos( date( 'W' ), 'Saturday Sunday' ) > 0 then say 'Today is a weekend day' else say 'Today is a weekday' |
Can be reduced to:
say 'Today is a week' , || copies( 'end ', wordpos( date( 'W' ), 'Saturday Sunday' ) > 0 ) , || 'day' |
The WORD built-in function can be used to use one word or another. Suppose, you need to scroll up or down N lines. You can replace the following:
if direction = 'DOWN' then topline = topline + N else topline = topline - N |
Can be reduced to:
topline = topline + ( N * word( '-1 1', 1 + ( direction = 'DOWN' ) ) |
In the above, when direction is 'DOWN' N is multiplied by '1', otherwise it is multiplied by '-1'.
Similarly, you can use the WORD built-in function to replace:
select when ( date(b) // 7 ) = 0 then say 'Today is Monday' when ( date(b) // 7 ) = 1 then say 'Today is Tuesday' when ( date(b) // 7 ) = 2 then say 'Today is Wednesday' when ( date(b) // 7 ) = 3 then say 'Today is Thursday' when ( date(b) // 7 ) = 4 then say 'Today is Friday' when ( date(b) // 7 ) = 5 then say 'Today is Saturday' when ( date(b) // 7 ) = 6 then say 'Today is Sunday' otherwise nop /* n // 7 can only be 0 through 6 */ end |
By:
say 'Today is:' , word( 'Monday Tuesday Wednesday Thursday Thursday Saturday Sunday', , 1 + ( date(b) // 7 ) ) |
Suppose your program performs multiple simple assignments in a sequence. These can be combined into a single PARSE instruction. This is particularly helpful if the instructions are performed in a DO LOOP, or in an internal procedure that is executed many times. For example:
Replace:
do i=1 to 1000 x = 0 y = 1 text = '' call proc x, y, text end |
By:
do i=1 for 1000 parse value 0 1 with , x , /* becomes: 0 */ y , /* becomes: 1 */ text /* becomes: '' */ call proc x, y, text end |
A common REXX idiom is to store a series of values in STEM.1, STEM.2, ... and to store the number of items in STEM.0.
For example the following assigns a sequence of lines to a series of compound variables with common stem LINE..
line. = '' line.0 = 0 do lines() lineIndex = line.0 + 1 line.0 = lineIndex line.lineIndex = linein() end |
The three instructions in the loop above can be replaced with a single instruction:
line. = '' line.0 = 0 do lines() parse value ( line.0 + 1 ) linein() with , lineIndex . 1 line.0 line.lineIndex end |
Assume the first line that is read in is: 'Line 1', and that line.0 is 0. Then, the value that is parsed is:
( line.0 + 1 ) linein()
Which becomes:
1 Line 1
The parse template is:
lineIndex . 1 line.0 line.lineIndex
The parse template is processed as follows:
lineIndex is assigned the first word of the value: 1
. is a placeholder that absorbs the rest of the value
1 is a directive to start parsing from the beginning again
line.0 is assigned the first word of the value: 1
line.lineIndex becomes line.1 which is assigned the rest of the value: Line 1
The net result of parsing is:
line.0 is: 1
line.1 is: Line 1
Suppose you have a program that is processing a series of 3 byte segments within an argument string. This can be reduced by parsing through the argument string directly, instead of repeatedly reducing a copy of the argument string. This avoids making unnecessary copies of the argument string. Significant performance gains can ensue if the argument string is lengthy.
Replace:
s = arg(1) do i=1 to length( s ) by 3 seg = left( s, 3 ) s = substr( s, 4 ) ... end |
By:
do offset=1 to length( arg( 1 ) ) by 3 parse arg =(offset) seg +3 ... end |
The following instruction assigns the 1st word in 'str' to variable 'wd1', and the remaining words to 'str'. This reduces the number of instructions that are performed within the loop.
parse var str wd1 str
Replace:
s = 'I think therefore I am.' do i=1 to words( s ) wd1 = word( s, 1 ) s = subword( s, 2 ) ... end |
By:
s = 'I think therefore I am.' do while s <> '' parse var s wd1 s ... end |
If the control variable of a DO LOOP is stepped by one during each iteration, then a DO .. FOR .. is recommended. This avoids unnecessary comparisons with the limit value, at the end of each loop iteration.
Replace:
do i=1 to 1000 ... end |
By:
do i=1 for 1000 ... end |
If the control variable associated with a DO LOOP is not referenced within the loop, it can be eliminated. This avoids fetching the value of the control variable, stepping the value by 1, and then replacing the variable's value.
Replace:
do i=1 to 1000 ... end |
By:
do 1000 ... end |
BEWARE: there could be internal procedure invocations within the loop, which might reference the control variable via symbol exposure. For example:
sum = 0 do i=1 to 10 call iproc end say sum return iproc : /* all of the caller's variables are exposed */ sum = sum + i /* <<< -- this references symbol I */ return |
Within a SELECT instruction group, order the WHEN clauses by frequency of use.
Replace:
select when lessFrequent then ... when moreFrequent then ... otherwise ... end |
By:
select when moreFrequent then ... when lessFrequent then ... otherwise ... end |
Often a similar expression is used in SELECT/WHEN clauses or a series of IF..ELSE IF..ELSE IF... instructions. REXX analyzes each of these expressions in sequence. On average, half of the expressions are evaluated. A significant improvement can be attained if only one expression were evaluated instead. This can be achieved by using SIGNAL VALUE to pass control to a series of labels instead.
Replace:
select when command = 'LEFT' then ...process 'LEFT' request when command = 'RIGHT' then ...process 'RIGHT' request when command = 'UP' then ...process 'UP' request when command = 'DOWN' then ...process 'DOWN' request otherwise ...process unrecognized command end |
By:
signal ON syntax NAME _unrecognizedCommand signal value '_'command _left : ...process 'LEFT' request signal _resume _right : ...process 'RIGHT' request signal _resume _up : ...process 'UP' request signal _resume _down : ...process 'DOWN' request signal _resume _unrecognizedCommand : ...process unrecognized command _resume : signal ON syntax |
Note: the above labels are preceded by an underscore, because 'LEFT' and 'RIGHT' are REXX built-in functions. If these labels were not preceded by an underscore, then control would be erroneously passed to these labels when left() or right() requests are performed.
Note: SIGNAL VALUE will cause all pending control structures (DO loops, DO groups, IF contructs, etc.) to be terminated. You can keep these active by putting the SIGNAL VALUE sequence in an internal procedure, that has all variables exposed:
do forever say 'Enter command request:' parse linein command rest call processCommand end /* forever */ exit processCommand : /* procedure */ signal ON syntax NAME _unrecognizedCommand signal value '_'command _left : ...process 'LEFT' request signal _resume /* or return */ _right : ...process 'RIGHT' request signal _resume /* or return */ _up : ...process 'UP' request signal _resume /* or return */ _down : ...process 'DOWN' request signal _resume /* or return */ _unrecognizedCommand : ...process unrecognized command _resume : signal ON syntax ... conclude command processing return |
Click the following link if you have any additional performance hints that you would like to share
with other REXX programmers:
I have a hint to share.