Avoid Large Local Variables in Modules
July 25, 2007 Ted Holt
In two previous articles, I dealt with some program design practices that can cause performance problems. Today, I deal with another area where large variables can have a derogatory impact on performance, and I show you a couple of ways to get around the problem. I use RPG for my examples, but the principle applies to any language. Let’s say we need three string-handling subprocedures–I’ll call them DoThis, DoThat, and DoTheOther–that will be used in many programs. Since they’re used so widely, let’s put them into a service program. Let’s say further that each of these subprocedures accepts two 64K variable-length character parameters, and that each one needs a 64K character work variable. Here’s the source for module MYSRVPGM. H nomain /copy prototypes,MYSRVPGM P DoThis b export D pi D* parameters D OneString 65535a varying const D AnotherString 65535a varying const D* local variables D WorkString s 65535a varying /free WorkString = OneString; Return; /end-free P e P DoThat b export D pi D* parameters D OneString 65535a varying const D AnotherString 65535a varying const D* local variables D WorkString s 65535a varying /free WorkString = OneString; Return; /end-free P e P DoTheOther b export D pi D* parameters D OneString 65535a varying const D AnotherString 65535a varying const D* local variables D WorkString s 65535a varying /free WorkString = OneString; Return; /end-free P e If this were a real application, the subprocedures would do something, of course. These stubs will serve to help us get a rough idea of how large local variables can affect performance. Here’s part of a typical caller. /free *inlr = *on; for Index = 1 to Limit; DoThis (String1: String2); DoThat (String1: String2); DoTheOther (String1: String2); endfor; Each time DoThis, DoThat, or DoTheOther is invoked, the system has to allocate a 64-kilobyte variable. When the subprocedure returns to the caller, the system deallocates the local variable. This allocation and deallocation is of no importance for small variables, but can be expensive when the variable is large, as in this case. In order to get a rough idea of how performance might suffer, I submitted a calling routine to batch and looked to see how many CPU seconds the system would take. This gave me a baseline to compare other techniques against. Here are the figures in CPU seconds, according to the job log.
There is nothing scientific about these measurements. All this table really says is that allocation of a large local variable is no big deal if you don’t create it too often. I realize that some programs do run for hours, processing hundreds of thousands, if not millions of records, so the idea of invoking three subprocedures 10 million times is not unrealistic, but I do think it is atypical of most programs. In cases where 75 CPU seconds of run time is a problem, we need to find some way to amputate some of that run time. Here are two other approaches you might take to improve performance. 1. Use global variables, rather than local variables, in the module. In the following variation of the module, all three subprocedures use the same copy of a global variable, which is allocated only once, when the service program is first activated. H nomain /copy prototypes,MYSRVPGM D WorkString s 65535a varying P DoThis b export D pi D* parameters D OneString 65535a varying const D AnotherString 65535a varying const /free WorkString = OneString; Return; /end-free P e P DoThat b export D pi D* parameters D OneString 65535a varying const D AnotherString 65535a varying const /free WorkString = OneString; Return; /end-free P e P DoTheOther b export D pi D* parameters D OneString 65535a varying const D AnotherString 65535a varying const /free WorkString = OneString; Return; /end-free P e I submitted the caller to batch with this service program, and found better results.
2. Use the STATIC keyword on the local variables. H nomain /copy prototypes,MYSRVPGM P DoThis b export D pi D* parameters D OneString 65535a varying const D AnotherString 65535a varying const D* local variables D WorkString s 65535a varying static /free WorkString = OneString; Return; /end-free P e P DoThat b export D pi D* parameters D OneString 65535a varying const D AnotherString 65535a varying const D* local variables D WorkString s 65535a varying static /free WorkString = OneString; Return; /end-free P e P DoTheOther b export D pi D* parameters D OneString 65535a varying const D AnotherString 65535a varying const D* local variables D WorkString s 65535a varying static /free WorkString = OneString; Return; /end-free P e Static variables are allocated once and retain their state across invocations. The system does not have to allocate and deallocate with each invocation of a subprocedure. The results looked equally as good as the results I got when I used a local variable.
Of the two approaches, I prefer the STATIC approach. It gives me the advantages of local variables without the possibility that two subprocedures might step on each other’s feet. The lesson I take is this: Use all the small local variables you want, but look for alternatives before declaring large variables in subprocedures. RELATED STORIES Parameter Passing and Performance Performance of Function Subprocedures
|