Tips to reduce static storage
The variables in an ILE RPG module or program can be divided into three storage (memory) classes: static, automatic, and heap (or dynamic.) These classes determine how and when storage is allocated, used and freed. If a program uses a lot of static storage, and has many instances active at once, performance may suffer.
For example, if an order entry program allocates almost 3MB of static storage (naming no names!), only a tiny fraction of which it normally uses on any one invocation, performance is likely to be an issue. Applying the techniques listed below could dramatically reduce the static storage requirements of such a program, without necessitating a total rewrite.
Here is a brief review of the different storage classes.
a) Comprise all global variables (those defined in the main procedure, if there is one), and local variables declared within a subprocedure, using the static keyword.
b) Include those implicitly defined as a result of F-specs and SQL preprocessor includes. These variables are global.
c) Are allocated at program initialization. Static variables are allocated in all modules within the program, and within any bound service programs (and any service programs bound to those service programs, and so on…)
d) Are freed (deallocated) when the activation group is reclaimed (if DFTACTGRP=*NO), or when the program ends with *INLR on, or RCLRSC is issued (if DFTACTGRP=*YES). This means that the storage for an ILE program with ACTGRP=*CALLER that is running in the default activation group cannot be freed until the job ends. (The default activation group cannot be reclaimed.)
Note: For a program compiled with DFTACTGRP(NO), global static variables are marked for re-initialization when the module or program ends with *INLR on. (This behavior differs from OPM and DFTACTGRP=*YES, where static storage is freed when the program ends with *INLR on.) Local static variables are not reinitialized.
a) Are declared within a subprocedure.
b) Are allocated on the stack when (and if) the subprocedure is called.
c) Are initialized each time the subprocedure is called.
d) Are freed when the subprocedure returns to its caller.
a) Are manually allocated and freed by the program. Storage is only used as and when it is needed.
b) Are not initialized when allocated.
c) Are freed when the activation group is reclaimed, if not already freed by the program.
We see from the above that static variables are allocated en bloc, whether they are immediately required or not. Automatic and heap variables, on the other hand, permit more granular control over their allocation.
ISeries 400 (now IBM i) memory management is very sophisticated. Nevertheless, all other things being equal, reducing the amount of static storage can mean faster initialization and less paging. This is especially significant if many instances of the program can be active at any one time — for example, an order entry program or a server job with multiple instances.
Here are some ideas for reducing static storage usage in an existing program. The tips concentrate mainly on very large fields.
1. Eliminate unreferenced fields. These are the fields against which message RNF7031 appears in a compilation listing. Not all such fields can be eliminated; some may be referenced only indirectly as part of a data structure, or may be unused file fields. However, some fields may be genuine deadwood, perhaps as a result of program maintenance, and can be safely removed.
2. Consider declaring any large fields or data structures as based, dynamically allocating the storage when needed. This is effective if the variables are used only in some circumstances. If the circumstances do not arise, the storage is not allocated. Program design will dictate when, or if, the storage should be deallocated. This technique can also be used when an externally described data structure is used to define host variables for use by SQL. Allocate the storage only when the file is needed.
3. Dynamically allocate large arrays or multiple occurrence data structures, if you do not know at compile-time how large they will be. For example, if an array will normally use around 100 elements, but could sometimes reach 1000 elements, declare the array as based, with 1000 elements, and allocate the actual storage once your program determines how large it should be. C runtime functions can be used to sort and search such an array — for more details see my earlier tip, “In search of a better Lookup?”
4. Consider sharing large compile-time or pre-runtime arrays and tables that hold static data. Normally, each instance of the program will have its own copy of such data. An alternative is to load the data into a user space. Then, declaring the array as based, call the QUSPTRUS API to place the address of the user space into the based on pointer. From here on, all accesses to the array remain the same. Net result: only one copy of the data exists, reducing overall storage usage.
5. Don’t overdo it. All of the above techniques, except the first, introduce some extra complexity into a program. This is worthwhile only if you can save significant amounts of static storage.
How can you determine the amount of static storage required by a program or module? The DSPPGM and DSPMOD commands may be used; specify DETAIL(*SIZE) to view size information only.
Finally, the Open List of Activation Attributes (QWVOLACT) API generates a list of all active programs in a given activation group, together with their static storage usage. This is useful in determining whether a program or service program is active. The Open List of Activation Group Attributes (QWVOLAGP) API can be used to generate a list of all the activation groups associated with a job.