Optimize Your RPG Code to Run Faster Using Static Variables PDF Print E-mail
Programming - RPG
Written by Thomas Snyder   
Wednesday, 15 June 2011 00:00

Support MC Press - Visit Our Sponsors

Forums Sponsor

POPULAR FORUMS

Forums

Search Sponsor

Search

Take advantage of global, static, and non-static variable attributes within subroutines and procedures.

 

IBM has always been good at giving us all the tools that we need in one box and ensuring that they run on the box reliably and efficiently. Since RPG has evolved into the ILE environment, we have been given more capabilities in the realm of modular coding and encapsulation. With these new capabilities, you have to learn about what you're gaining, what you're losing, and what all the nuances are of the different coding styles. In this article, I intend to describe the scoping and attributes of variables with RPG procedures.

Procedure Encapsulation

One of the objectives of procedures is to provide the programmer with an encapsulated black box of functionality with no fears of having external resources reach beyond the protection of the procedure to cause unpredictable results. This is accomplished by making the internal variables of the procedure private to the procedure.

 

When the variables are made private to the procedure, they are no longer visible to code outside of the procedure. And if the same variable name is used within the procedure as a variable that is named outside of the procedure, they will each be allocated their own memory space and be independent of one another.

Procedure Encapsulation Versus Subroutine Global Variables

Encapsulation is a common goal for modern programming techniques, but it does take away some basic capabilities that you wouldn't have initially thought of before it became available. For example, suppose you want to keep track of how many times a subroutine was called. To provide a real-world example, let's say we have an account maintenance program that tracks how many accounts were updated during the duration of the execution of the program.

Using a Subroutine with Access to Global Variables

A simple program that counts the number of updates using subroutines could look like the following code:

 

     D dataStruct      DS

     D  counter                       7S 0

     D  otherVar                      7S 0

     D displayBytes    S             52A

      /free

       counter = *ZEROS;

       //---------------------------------------------------

       // User does maintenance, then updates account

       exsr updateAccount;

       displayBytes = 'Number of Records Updated: '

                    + %trim(%editc(counter: '3'));

       dsply displayBytes;

       //---------------------------------------------------

       // Lather, Rinse, Repeat

       exsr updateAccount;

       displayBytes = 'Number of Records Updated: '

                    + %trim(%editc(counter: '3'));

       dsply displayBytes;

       //---------------------------------------------------

       // Improper reuse of global variable

       // Because available, another programmer has

       // !!! Inadvertently reset the counter !!!

       clear dataStruct;

       dsply 'Unexpected Counter Reset.';

       //---------------------------------------------------

       // Lather, Rinse, Repeat

       exsr updateAccount;

       displayBytes = 'Number of Records Updated: '

                    + %trim(%editc(counter: '3'));

       dsply displayBytes;

       *inlr = *ON;

       //---------------------------------------------------------------

       // updateAccount: Updates Physical File Record(s) for Account

       //                count: Counts the number of times executed

       //----------------------------------------------------------------

       begsr updateAccount;

         counter = counter + 1;

        //----------------------------------------------------------

        // ... code ... code ... code ... code ....

        // .........

        // Do Lots of Time Consuming Logic to Update Account Here...

        // .........

        // ... code ... code ... code ... code ....

        //----------------------------------------------------------

       endsr;

      /end-free

 

When we run the program, we will get the following results. Take note that, because the subroutine is using global variables, the counter was inadvertently reset by code outside of the subroutine.

 

> CALL PGM(MCP042RPGA)                     

   DSPLY  Number of Records Updated: 1     

   DSPLY  Number of Records Updated: 2     

   DSPLY  Unexpected Counter Reset.     

   DSPLY  Number of Records Updated: 1      

 

In this code, you can see that the counter variable is defined globally and is being used only within the subroutine. Because the counter variable is available globally, when another programmer comes into the program and clears the dataStruct data structure, the counter variable is reset to zero within the code, creating undesired results on the value of the counter accumulator.

Using a Procedure with Access to Global Variables

Next, I will convert the subroutine into a procedure. You can see that I am still accessing the global counter variable.

 

     D dataStruct      DS

     D  counter                       7S 0

     D  otherVar                      7S 0

     D displayBytes    S             52A

     D* Prototype for updateAccount procedure

     D updateAccount...

     D                 PR             7S 0

      /free

       counter = *ZEROS;

       //---------------------------------------------------

       // User does maintenance, then updates account

       counter = updateAccount();

       displayBytes = 'Number of Records Updated: '

                    + %trim(%editc(counter: '3'));

       dsply displayBytes;

       //---------------------------------------------------

       // Lather, Rinse, Repeat

       counter = updateAccount();

       displayBytes = 'Number of Records Updated: '

                    + %trim(%editc(counter: '3'));

       dsply displayBytes;

       //---------------------------------------------------

       // Improper reuse of global variable

       // Because available, another programmer has

       // !!! Inadvertently reset the counter !!!

       clear dataStruct;

       dsply 'Unexpected Counter Reset.';

       //---------------------------------------------------

       // Lather, Rinse, Repeat

       counter = updateAccount();

       displayBytes = 'Number of Records Updated: '

                    + %trim(%editc(counter: '3'));

       dsply displayBytes;

       *inlr = *ON;

      /end-free

      *-----------------------------------------------------------------

      * updateAccount: Updates Physical File Record(s) for Account

      *                count: Counts the number of times executed

      *-----------------------------------------------------------------

     P updateAccount...

     P                 B                   EXPORT

     D updateAccount...

     D                 PI             7S 0

      /free

        counter = counter + 1;

        //----------------------------------------------------------

        // ... code ... code ... code ... code ....

        // .........

        // Do Lots of Time Consuming Logic to Update Account Here...

        // .........

        // ... code ... code ... code ... code ....

        //----------------------------------------------------------

        return counter;

      /end-free

     P                 E

 

Even though we've created a procedure, our data is still not encapsulated because the procedure is dependent upon variables that are globally accessible throughout the program. So, when the counter variable is reset in the main program, it causes the expected results of the procedure to become broken.

Using a Procedure with a Protected Variable Inside the Procedure

In our next revision, I will define a local variable within the procedure.

 

      *-----------------------------------------------------------------

      * updateAccount: Updates Physical File Record(s) for Account

      *                count: Counts the number of times executed

      *-----------------------------------------------------------------

     P updateAccount...

     P                 B                   EXPORT

     D updateAccount...

     D                 PI             7S 0

     D* Procedure Variables

     D   counter       S              7S 0

      /free

        counter = counter + 1;

        //----------------------------------------------------------

        // ... code ... code ... code ... code ....

        // .........

        // Do Lots of Time Consuming Logic to Update Account Here...

        // .........

        // ... code ... code ... code ... code ....

        //----------------------------------------------------------

        return counter;

      /end-free

     P                 E

 

When the counter variable is defined locally in the procedure, it will be reinitialized every time the procedure is called. Here are the results when this version of the program is run:

 

> CALL PGM(MCP042RPGC)                      

  DSPLY  Number of Records Updated: 1       

  DSPLY  Number of Records Updated: 1       

  DSPLY  Unexpected Counter Reset.       

  DSPLY  Number of Records Updated: 1       

 

The variable is now encapsulated, but it still does not give us our expected results. This is because the variables defined within the procedure are created on every call to the procedure and destroyed when it is complete. So the value of the counter accumulator never goes beyond one.

Using a Procedure with a Static Variable

Here is where the static variable becomes the solution to our problem. When we define a static variable, it is only initialized once and it is only available within the procedure.

 

     D dataStruct      DS

     D  counter                       7S 0 inz(0)

     D  counterG                      7S 0 inz(0)

     D  otherVar                      7S 0

     D displayBytes    S             52A

     D* Prototype for updateAccount procedure

     D updateAccount...

     D                 PR             7S 0

      /free

       //---------------------------------------------------

       // User does maintenance, then updates account

       counter = updateAccount();

       counterG = counterG + 1;

       displayBytes = 'Global: '

                    + %trim(%editc(counterG: '3'))

                    + ', Procedure Static: '

                    + %trim(%editc(counter: '3'));

       dsply displayBytes;

       //---------------------------------------------------

       // Lather, Rinse, Repeat

       counter = updateAccount();

       counterG = counterG + 1;

       displayBytes = 'Global: '

                    + %trim(%editc(counterG: '3'))

                    + ', Procedure Static: '

                    + %trim(%editc(counter: '3'));

       dsply displayBytes;

       //---------------------------------------------------

       // Improper reuse of global variable

       // Because available, another programmer has

       // !!! Inadvertently reset the counter !!!

       clear dataStruct;

       dsply 'Unexpected Counter Reset.';

       //---------------------------------------------------

       // Lather, Rinse, Repeat

       counter = updateAccount();

       counterG = counterG + 1;

       displayBytes = 'Global: '

                    + %trim(%editc(counterG: '3'))

                    + ', Procedure Static: '

                    + %trim(%editc(counter: '3'));

       dsply displayBytes;

       *inlr = *ON;

      /end-free

      *-----------------------------------------------------------------

      * updateAccount: Updates Physical File Record(s) for Account

      *                count: Counts the number of times executed

      *-----------------------------------------------------------------

     P updateAccount...

     P                 B                   EXPORT

     D updateAccount...

     D                 PI             7S 0

     D* Procedure Variables

     D   counter       S              7S 0 static inz(0)

      /free

        counter = counter + 1;

        //----------------------------------------------------------

        // ... code ... code ... code ... code ....

        // .........

        // Do Lots of Time Consuming Logic to Update Account Here...

        // .........

        // ... code ... code ... code ... code ....

        //----------------------------------------------------------

        return counter;

      /end-free

     P                 E

 

In this program, a new variable called counterG has been added to show how the global variable is incremented versus the procedure static variable. Now when we run the program, we get the desired results on the procedure static variable.

 

   > CALL PGM(MCP042RPGD)                       

     DSPLY  Global: 1, Procedure Static: 1      

     DSPLY  Global: 2, Procedure Static: 2      

     DSPLY  Unexpected Counter Reset.        

     DSPLY  Global: 1, Procedure Static: 3      

 

We have a protected variable within the procedure that has the same name as a global variable. When the global variable or the static procedure variable is changed, it has no impact on the other. So we've achieved encapsulation and we've also supported our accumulator functionality.

The Difference Between Global Variables and Static Procedure Variables

Global variables are considered to be static variables within the program because they are only initialized once, when the program starts. The difference is that the static variables will not be reset until the activation group has been reclaimed.

 

If we run the program a second time, we can see that the global counters have the exact same values as expected, but we can also see that the procedure static variables will pick up where they left off from the last time the program was called even though the *INLR indicator was set on at the end of the first call. So, instead of the static procedure variable restarting at 1, it continues where the previous call left off at 3 to begin at 4.

 

> CALL PGM(MCP042RPGD)                        

   DSPLY  Global: 1, Procedure Static: 4       

   DSPLY  Global: 2, Procedure Static: 5       

   DSPLY  Unexpected Counter Reset.         

   DSPLY  Global: 1, Procedure Static: 6

 

If we reclaim the activation group, we can now see that the procedure static variables have been reset.

       

 > RCLACTGRP ACTGRP(*ELIGIBLE)                 

   Activation group QILE deleted.           

 > CALL PGM(MCP042RPGD)                        

   DSPLY  Global: 1, Procedure Static: 1       

   DSPLY  Global: 2, Procedure Static: 2       

   DSPLY  Unexpected Counter Reset.         

   DSPLY  Global: 1, Procedure Static: 3       

Other Potential Uses for Static Variables

Besides using static variables as simple accumulators as illustrated above, you could also use static variables to retain the last used value to return cached results. The sample code will be modified to store the last used account number to determine if the CPU-intensive logic needs to be executed. If the code could use data that is not required to be real-time data between calls, then you could just reuse the results from the last call.

 

      …

    /free

       //---------------------------------------------------

       // User does maintenance, then updates account

       counter = updateAccount(400);

       …

  

       //---------------------------------------------------

       // Lather, Rinse, Repeat

       counter = updateAccount(400);

       …

 

       //---------------------------------------------------

       // Lather, Rinse, Repeat

       counter = updateAccount(400);

       …

 

       *inlr = *ON;

      /end-free

      *-----------------------------------------------------------------

      * updateAccount: Updates Physical File Record(s) for Account

      *                count: Counts the number of times executed

      *-----------------------------------------------------------------

     P updateAccount...

     P                 B                   EXPORT

     D updateAccount...

     D                 PI             7S 0

     D  argAccount                    6S 0 const

     D* Procedure Variables

     D   counter       S              7S 0 static inz(0)

     D   svAccount     S              6S 0 static inz(0)

      /free

        counter = counter + 1;

        if (argAccount = svAccount);

          dsply 'Saving Time Using Cache...';

        else;

          dsply 'Executing CPU Intensive Logic...';

          //----------------------------------------------------------

          // ... code ... code ... code ... code ....

          // .........

          // Do Lots of Time Consuming Logic to Update Account Here...

          // .........

          // ... code ... code ... code ... code ....

          //----------------------------------------------------------

        endif;

        svAccount = argAccount;

        return counter;

      /end-free

     P                 E 

 

Now if you were to run the program to emulate a series of repeated calls to the same account, which could happen when you're running a report, then you could see that the CPU-intensive logic is only executed the first time; for all subsequent calls, it will reuse the values computed from the previous call. And it's all encapsulated within the subprocedure.

 

   > CALL PGM(MCP042RPGE)                           

     DSPLY  Executing CPU Intensive Logic...     

     DSPLY  Global: 1, Procedure Static: 1          

     DSPLY  Saving Time Using Cache...           

     DSPLY  Global: 2, Procedure Static: 2          

     DSPLY  Unexpected Counter Reset.            

     DSPLY  Saving Time Using Cache...           

     DSPLY  Global: 1, Procedure Static: 3          

 

Another possible use could be when you're using a buffer that is being used for a file read that is returning portions of the read file and advancing through the data with each call to the procedure.

 

Or another possible option that I have used static variables for would be to identify whether a multiple-member file was already open or not and then decide whether the file needs to be overridden to another member or can use the already-open file member from the previous read.

Download the Code

You can download the code used in this article by clicking here.

 

as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7, V6R1


Thomas Snyder
About the Author:

Tom Snyder has a diverse spectrum of programming experience encompassing IBM technologies, open-source, Apple, and Microsoft and utilizing these technologies with applications on the server, on the web, or on mobile devices.

 

Tom has over 20 years experience as a software developer in various environments, primarily in RPG, Java, C#, and PHP and holds certifications in Java from Sun and PHP from Zend. Prior to software development, Tom worked as a Hardware Engineer at Intel and is a proud United States Naval Veteran Submariner who served aboard the USS Whale SSN638 submarine.

 

Tom is the best-selling author of Advanced Integrated RPG, which covers the latest programming techniques for RPG ILE and Java to utilize open-source technologies.

 

Originally from and currently residing in Scranton, Pennsylvania. Tom is currently involved in a Mobile Application Start-up company named JoltRabbit LLC.

 

 


MC Press books written by Thomas Snyder available now on the MC Press Bookstore.

 

Advanced, Integrated RPG Advanced, Integrated RPG

This book shows you how to take advantage of the latest technologies from within existing RPG applications.

List Price $79.95
Now On Sale
 

 

Read More >>
Last Updated on Friday, 17 June 2011 08:13
 
User Rating: / 9
PoorBest 
   MC-STORE.COM