24
Wed, Jul
0 New Articles

Optimize Your RPG Code to Run Faster Using Static Variables

RPG
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

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

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

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

Tom is the bestselling author of Advanced, Integrated RPG, which covers the latest programming techniques for RPG ILE and Java to use open-source technologies. His latest book, co-written with Vedish Shah, is Extract, Transform, and Load with SQL Server Integration Services.

Originally from and currently residing in Scranton, Pennsylvania, Tom is currently involved in a mobile application startup company, JoltRabbit LLC.


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

Advanced, Integrated RPG Advanced, Integrated RPG
See how to take advantage of the latest technologies from within existing RPG applications.
List Price $79.95

Now On Sale

Extract, Transform, and Load with SQL Server Integration Services Extract, Transform, and Load with SQL Server Integration Services
Learn how to implement Microsoft’s SQL Server Integration Services for business applications.
List Price $79.95

Now On Sale

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: