Question:
Concerning
your article "Procedures and Performance" (Article Id. 54505) in
SystemiNetwork, maybe you could improve the performance by using the keyword
"STATIC" at the local variable declaration?
Answer:
Good
question. I had the question on my finger tip, but at the time I wrote this
article my target was not to optimize the code, it was to show how painfully
was the error I did. Done. And now, it's time to show how to use correctly
STATIC.
I've try
STATIC on my test case, it improves dramatically performance.
And there
is nothing linked to recursivity, as I supposed when reading ILE RPG Reference,
just reusability.
Excerpt
from SC09-2508-05 :
For a local variable of a subprocedure, the STATIC keyword specifies that the data item is to be stored in static storage, and thereby hold its value across calls to the procedure in which it is defined. The keyword can only be used within a subprocedure. All global fields are static.
The data item is initialized when the program or service program it is
contained in is first activated. It is not reinitialized again, even if
reinitialization occurs for global definitions as part of normal cycle
processing.
If STATIC is not specified, then any locally defined
data item is stored in automatic storage. Data stored in automatic storage is
initialized at the beginning of every call. When a procedure is called
recursively, each invocation gets its own copy of the storage.
The most
important part of this excerpt is It is not
reinitialized again.
with STATIC,
all procs have approx the same duration :
Inline
CPU time in nanoseconds |
Inline
elapsed time in nanoseconds |
Procedure
name |
without
static |
|
|
706,162 |
60,980,882 |
PR1 |
174,489,762 |
1,113,089,427 |
PR3 |
with
static |
|
|
832,095 |
85,185,077 |
PR1 |
686,321 |
69,075,643 |
PR3 |
with
static, there's only one variable, no matter how many instances (level of
recursivity) of the procedure there are. So if you call the procedure
recursively and change the variable, it'll still be changed when you return to
your caller. A static variable IS a global variable, except that it's not available outside of the
procedure it's declared in.
This is
also why it improves performance.
AS
explained in the RPG Reference, The data item is
initialized when the program or service program it is contained in is first
activated.
That is to
say, most of the work has be done at compilation time, to have the program
loader to do as less as possible. Nothing has to be allocated or initialized
when the procedure starts. The variable is already there.
However, in
many cases (though, not always) people want their variables to be
"fresh" (i.e. start with blanks or zeroes) at the start of their
subprocedure. If you use STATIC, it will
maintain it's value from the last call, so you'd have to manually move *blanks
or *zeros to it to clear it. If you do
that, do you still get better performance?
If so, by how much?
Another
good question, but before starting to show if it's better to initialize
yourself or let the system do the work, meditate a moment on "How many
times do you need to get the value of a variable before having loaded it
?" because it's what the initialization step do.
To measure
this way of initialization, I've updated my test case to add a CLEAR of each
static variable at the beginning of each procedure :
Sample :
Basic case:
P pr3 b
D pr3 pi
D data 65535 const varying
D wrk1 s 65535 varying
D wrk2 s 65535
/free
i=i;
/end-free
P e
Static
case:
P pr3 b
D pr3 pi
D data 65535 const varying
D wrk1 s 65535 varying static
D wrk2 s 65535 static
/free
i=i;
/end-free
P e
Static and
clear case
P pr3 b
D pr3 pi
D data 65535 const varying
D wrk1 s 65535 varying static
D wrk2 s 65535 static
/free
clear wrk1;
clear wrk2;
i=i;
/end-free
P e
Here is the
raw data pick from PEX querying
|
|
basic |
static |
static clear |
PR1 |
0 |
647,374 |
857,800 |
797,558 |
PR2 |
0 |
726,615 |
724,994 |
719,779 |
PR4 |
20 |
711,349 |
637,665 |
965,562 |
PR14 |
120 |
961,893 |
579,518 |
909,964 |
PR13 |
250 |
1,012,788 |
599,375 |
1,979,497 |
PR12 |
500 |
1,991,179 |
557,065 |
1,723,770 |
PR11 |
1,000 |
2,414,745 |
556,393 |
2,647,059 |
PR10 |
2,000 |
3,681,011 |
503,616 |
3,830,128 |
PR9 |
4,000 |
6,238,758 |
622,569 |
6,153,956 |
PR8 |
8,000 |
11,777,871 |
537,384 |
11,746,635 |
PR7 |
16,000 |
32,614,964 |
580,419 |
42,885,212 |
PR6 |
32,000 |
65,810,440 |
547,675 |
86,496,278 |
PR5 |
64,000 |
165,727,434 |
1,213,270 |
137,154,020 |
PR3 |
128,000 |
210,528,671 |
960,006 |
139,650,891 |
PR15 |
128,000 |
79,396,397 |
887,121 |
1,049,137 |
PR16 |
128,000 |
303,216,710 |
559,160 |
276,187,999 |
PR15 and
PR16 are a variant of PR3 : PR3 has a 64k fix and a 64k varying field
There is an
erratic measure for the PR13 (255
bytes), but globally, static plus clear is equal to basic. IE it's not a good
idea to do ourselves the initialisation.
On the
other hand, it's a good idea to declare STATIC for each variable that don't
participate to a recursive call. Because I never need to get the value of a
variable before having loaded it. And I think it's the same for you.
You can
find in attached material