Performance tips, guidelines and techniques

Performance and Benchmark Group

Table of Contents

1.Introduction

1.1Document Purpose

2.Performance Considerations when using application classes

2.1Inheritance Levels

2.1.1.1Suggested Alternative(s)

2.2Avoid un-necessary object instantiations

2.2.1.1Suggested Alternative(s)

2.3Use of global variables and component variables within application classes

2.4Singletons

3.PeopleCode Performance Tips

3.1General Performance Issues

3.2Performance Tips

3.2.1SOME EXAMPLES

4.Online coding techniques for performance......

4.1Component/Page/Field Coding Guidelines

4.1.1Place PeopleCode At The Right Level (Component, Page or Field level?)

4.1.2Consolidate PeopleCode To One Field.

4.1.3Use Rowsets To Store Commonly Shared Or Frequently Used Data.

4.1.4Do Not Invoke AutoSelect For Loading Rows Into A Grid Or Scroll Area.

4.1.5Manually Limit The Number Of Rows Selected Into A Grid Or Scroll Area – Chunking.

4.1.6Limit Use Of Related Display Fields In Grids

4.1.7Select Data Onto Pages Of A Component As Needed

4.2Online PeopleCode Coding Guidelines

4.2.1Store and Reuse Common Functions.

4.2.2Load And Execute Functions Only When Necessary.

4.2.3Use Component Variables To Enhance Sharing Of Data

4.2.4Use Arrays To Store Frequently Accessed Data

4.2.5Use Search Arrays To Access Data In Arrays

4.2.6Use Search Arrays To Access Data In Rowsets

4.2.7Properly Declare Methods In Application Classes

4.3Online SQL Coding Guidelines

4.3.1Store And Reuse Common SQL Statements.

4.3.2Execute SQL Statements Only When Necessary.

4.3.3Decrease SQL Statements Executed In A Loop.

4.3.4Check For Value Of “NEXT” During ADD Transaction.

4.3.5Use Views With Care.

4.3.6Close SQL objects after used.

4.4General Rule Of Thumb For Coding Online Transactions

4.4.1Minimize Server Trips

5.Top 10 best practices for batch program design

5.1Do not abuse the usage of temporary tables

5.2Use set based processing as much as possible

5.3Avoid using PeopleCode in a loop

5.4Minimize duration of lock on hot spot tables

5.5Design to support parallel processing

5.6Avoid complex SQL's (SQL’s with more than 4 nested sub queries, sql with more than 5 table joins)

5.7Commit frequently (but not after each statement)

5.8Add restart logic if unit of work can be large

5.9Sharing code by batch and online

5.10Use parameter markers for Sql's that is being executed in a loop to avoid database compilation time.

6.Appendix A

6.1Inheritance Levels tests

6.2Object instantiation analysis

6.2.1.1Suggested Alternative

7.Appendix B – PeopleCode FAQ’s

8.Appendix C - PeopleCode Application Classes – Design and Programming Best Practices

8.1Overview

8.2What do app classes bring to the table?

8.3Why decouple code from the GUI?

8.4What is GUI-specific code?

8.5How do app classes increase maintainability?

8.6Abstract base classes

8.7Generic base classes

8.8Utility classes

8.9App class structure

8.10Imports

8.11Class declaration

8.12Private Interface

8.13Properties

8.14Instance variables

8.15Constants and readonly properties

8.16Method parameters

8.17Other variables

8.18Exceptions

8.19Documentation conventions

8.20Method and class header comments

8.21Summary

1.Introduction

1.1Document Purpose

The intent of this document is outline PeopleCode application classes design and programming best practices, key performance considerations when using application classes, general PeopleCode performance tips and online coding techniques.

2.Performance Considerations when using application classes

2.1Inheritance Levels

An application class hierarchy or inheritance tree can be as deep as needed. There are no limitations as such on the levels of inheritance.

However, depending on the inhertiance level, instantiating a subclass is resource intensive and time consuming. The cost for instantiating a subclass grows higher as the inheritance hierarchy goes deeper (See Appendix A Section 4.1).

2.1.1.1Suggested Alternative(s)
  • Assess the functionality in the base classes and determine if there is scope for flattening the inheritance hierarchy. Avoid too many levels of inheritance, as class construction is expensive.
  • Use composition instead of inheritance. Instead of extending an existing class, give your new class a private field that references an instance of the existing class. The calls to the methods in this new class are delegated to corresponding methods in the contained instance.

Inheritance is appropriate only when you have an “is-a” relationship between two classes.

2.2Avoid un-necessary object instantiations

Programs that create many objects potentially have performance implications due extensive resource utilization (CPU, memory etc).

2.2.1.1Suggested Alternative(s)
  • Design before code: Follow basic object oriented design principles to design application specific class hierarchy (encapsulation, what is a class versus what class behavior is). See Appendix A Section 4.2 on some observations for a transaction. Since the seeds of many performance issues are sown at design time, it pays to be aware of the potential performance impact of certain design decisions.

2.3Use of global variables and component variables within application classes

Peoplecode allows you to declare global variables in application class code (below end-class, alongside method body declarations). From a purist standpoint this breaks encapsulation however; this can be leveraged to implement singletons across user sessions. One classic example where this technique can be beneficial is in the area where:

  • There is a Constants class that contains constant values that can be used across entire application. So instead of having all objects that need a reference to a Constant instance instantiate this class, one could create a BaseObject that all classes can inherit off and have the Constant class declared as a global variable in the BaseObject. Even though the globals variables traverse between the web and application server for every request the overhead is close to minimal due to the size of this Constant object compared to instantiating a Constant class every time.

Do not use globals to store large datasets. This data gets carried back and forth during server trips and adds to the serialization cost and becomes resource intensive.

Also if you plan to use globals, make sure the stale data is flushed periodically

Use component variables declared in application classes to share common data between

application classes which are invoked during a single component transaction. For example, if there is a string value that needs to be passed between 2 applications classes, then a string variable would be declared in both application classes:

Component string &c_share_string;

This declaration must appear at the very top of the application class.

To fill the string variable or to query the string variable, each of the application classes would contain PeopleCode such as:

If &c_share_string = null Then

&c_share_string = Create share_string();

Else

/* do nothing*/

End-If;

&local_string = &share_string;

2.4Singletons

The singleton pattern i.e. a single instance of a class per app. serv. process cannot be implemented using pplcd/application class constructs available. However, a user session based singletons can be accomplished via use of global member variables in an application class. This is only true for frequently used lightweight objects like Constants, common shared Utilities since these global objects traverse between the web and application server. This prevents un-necessary object instantiations.

Use funclibs to return instances over creating wrapper objects to return the instance.

3.PeopleCode Performance Tips

This section serves as a resource in writing good PeopleCode programs that perform well. PeopleCode has a long history and has evolved quite a long way from a basic scripting language (see PeopleCode 1.0 Specifications). PeopleCode consists of a compiler that compiles code for the PeopleCode "virtual machine" and an evaluator that interprets this intermediate code. As an interpreted language it can never compete with languages that have sophisticated compilers that produce optimized code that runs on the native machine.

3.1General Performance Issues

PeopleCode is intended as an auxiliary programming language and is not meant to supplant definitional approaches to application development. Computationally intensive programming in PeopleCode is guaranteed to perform poorly. Choose the right tool for the job!

Algorithm design is always an important issue. Algorithms that do not scale well (sometimes these are referred to as "n squared" meaning that the algorithm's cost does not scale linearly with the size of the problem) will perform poorly in any language and will perform very poorly in PeopleCode.

3.2Performance Tips

  1. Auto-declared variables: One of the conveniences of PeopleCode is that you do not have to declare your variables before you use them. The variable becomes a chameleon taking on the type of the value it is assigned. This convenience has a price in that you lose type checking at compile time, which can lead to problems at run time.

Example: When you validate or save your PeopleCode in the designer take notice of the auto-declared messages. Ask yourself if that is what you intended?

  1. Precise is good: Declaring variable types specifically. This is a follow on of the previous point. Most of the time we know what types the variables we use should be so we should declare them of that type to begin with.

Example: If you know that a particular variable is going to be an Integer value why not declare it to be of that type in the first place. In this particular case you can get much better runtime performance. It is particularly effective for loop control variables (for &i ...) but has limited range (9-10 digits max), so must be used judiciously

Local Integer &Index;

For &Index = 1 to &Limit;

  1. We all need some good references: Be aware of and perhaps take advantage of reference parameters. In PeopleCode functions calls, parameters are passed by reference. This means a reference to the value is passed instead of the value itself. In the following code the function foo changes the value of &Str after the call. This can be a blessing and a curse.

Function Foo(&Par as String)

&Par = "Surprise";

end-function;

Local String &Str = "Hello";

Foo(&Str);

/* now &Str has the value "surprise" */

  1. Give me a break in the evaluate statement: The evaluate statement semantics are that the when clauses continue to be evaluated until the end of the evaluate statement or a "break" is encountered. If you have an evaluate statement with a number of when clauses and you only expect one of them to match, make sure you put a break statement there. Otherwise all the subsequent when clauses will continue to be evaluated. Note that your program is still correct. It is just that it is very inefficient at run time, particularly if you have a large number of when clauses and this statement is in a loop.
  1. Size matters - when it comes to your state: One of the keystones in the PIA architecture is that the app server is stateless. What this means is that when required, the state of your session is bundled up and exchanged between the appserver and the web server. For example on a user interaction (UI) the whole state, including your PeopleCode state, has to be serialized to the web server and then once the UI has completed, that state is de-serialized in the app server so that your application can continue. What can you do about this? A couple of things. First, by being aware of this you can watch the size of PeopleCode objects you create (strings, arrays etc.) to make sure they are only as big as you need them to be. Second, if you are going to have some UI, be aware that all your PeopleCode state will need to be serialized across that interaction. You might be able to change the logic of your program to minimize the state. For example if you were building up a large string (a couple of Megabytes) and then doing some UI, you might be able to change your program logic to build the string after the UI.
  1. Say it once: The PeopleCode compiler is not an optimizing compiler unlike some current compilers for languages like C++. For example it does not do common sub-expression analysis. So sometimes if you have a complicated bit of PeopleCode that is used a lot you can isolate the common expression yourself. This can make your code look cleaner and make your code faster especially it it’s in a loop. In the example below notice how the common sub expression is broken out.

Example:

/*---- For this customer, setup time on B is influenced by

*---- the machine flavors of A. */

&r_machine = &rs(&idB).GetRecord(Record.MACHINE_INFO);

If (&typeA = "F") And (&typeB == "U") Then

&r_machine.SETUP_TIME.Value = 50;

Else

&r_machine.SETUP_TIME.Value = 10;

End-If;

  1. Think about what is really going on: can you spot the implicit conversions? The most common ones are those going from a character string to a number and vice versa. Now you might be able to do nothing about this but by being aware of it you might be able to spot opportunities for performance improvement. In the code below two character strings are converted into numeric values before the difference is taken. Now if this code was in a loop, and one of the values did not change it would be a significant performance gain to do the conversion once as the second statements illustrate.

&Diff = &R1.QE_EMPLID.Value - &R2.QE_EMPID.Value;

&Original = &R1.QE_EMPLID.Value;

. . .

&Diff = &Original - &R2.QE_EMPID.Value;

  1. Choosing the right sequel style: In certain cases the one-shot SQLExec is what you want to do. In other cases your SQL could benefit greatly from being executed through a SQL object instead. This particularly applies if you can plan to execute the statement more than once with different bind parameters. The performance gain comes from compiling the statement once and executing it many times. SQL objects also have the ReuseCursor property, which can be used for further performance. More detail can be found in PeopleBooks.
  1. What is happening inside that loop? It always is worthwhile to examine loops in your code to see what is happening inside the loop. The key thing is not to have code in the loop that doesn’t need to be in the loop. For example if you are working with File objects and your file layout does not change there is no reason to set the file layout every time you go through the loop reading lines from the file. Set the file layout once, outside the loop.
  1. Are you done with that? Once you are done with an object reference, especially a global or component one, assign Null to it to get rid of the object. This allows the runtime to cleanup unused objects reducing the size of your PeopleCode state.
  1. Application class specific suggestions:

Simple properties (without get/set) are much more efficient than method calls. Be clear in your design what need to be simple properties, properties with get/set, and methods. Never make something a method that really should be a property.

Analyze your use of properties implemented with get/set. While PeopleCode properties are in a sense "first class" properties with more flexibility in that you can run PeopleCode to actually get and set their values, make sure you actually need get and set methods. If all you have is a normal property which is more of an instance variable then avoid get/set methods. In the example below (without the strikethrough!) by having get/set for the property SomeString you have made it much more inefficient to get/set that property since every property reference has to run some PeopleCode. Often this can creep in when properties are designed to be flexible at the beginning and never subsequently analyzed for whether getters/setters were really needed after all.

class Test

...

property String SomeString get set;

end-class;

get SomeString

return &SomeString;

end-get;

set SomeString

&SomeString = &NewValue;

end-set;

3.2.1SOME EXAMPLES

  1. Beware of the Rowset fill method aka "What not to do in an Application Engine PeopleCode step". Sometimes the algorithm is the issue. Below you'll see a real live PeopleCode program that adopts the approach: read all the data in one gulp into a rowset, process it row by row and update as necessary. This is a bad approach in general since you might run out of memory if the rowset if too large and you lose the general advantage of set-based programming.

Local Rowset &RS;

Local Record &REC;

Local SQL &SQL_UPDATE;

&REC_NAME1 = "Record." | SOME_AET.SOME_TMP;

&RS = CreateRowset(@(&REC_NAME1));

&LINE_NO = 1;

&NUM_ROWS = &RS.Fill("WHERE PROCESS_INSTANCE = :1 AND BUSINESS_UNIT = :2 AND TRANSACTION_GROUP = :3 AND ADJUST_TYPE = :4 ", SOME_AET.PROCESS_INSTANCE, SOME_AET.BUSINESS_UNIT, SOME_AET.TRANSACTION_GROUP, SOME_AET.ADJUST_TYPE);

For &I = 1 To &NUM_ROWS

&REC = &RS(&I).GetRecord(@(&REC_NAME1));

&REC.SOME_FIELD.Value = &LINE_NO;

&REC.Update();

&LINE_NO = &LINE_NO + 2;

End-For;

Notice the problems:

You might run out of memory in the fill method if the select potentially selects a large amount of data.

The Fill is selecting all the columns in the table when all that's being updated is one column.

This could be changed to read in the data one row at a time using a SQL object or using a similar algorithm but chunk the rowsets into manageable size through use of an appropriate where clause.

There are some rough numbers you can use to see how big the rowset would be: the overhead for a field buffer (in tools 8x) independent of any field data is ~88 bytes; the overhead for a record buffer is ~44 bytes; the overhead for a row is ~26bytes. So for a rowset with just one record/row the general rough formula would be:

mem = nrows * (row overhead + nrecords * ( rec overhead + nfields * ( field overhead) + average cummulative fielddata for all fields))

Plug in your numbers and you'll get a sense of how big your rowset might be.

  1. Some examples that illustrate how to change your code to "say it once"

BEFORE:

Local Rowset &RS_Level2;

Local Boolean &TrueOrFalse = (PSU_TASK_RSRC.COMPLETED_FLAG.Value = "N");

For &I = 1 To &RS_Level2.ActiveRowCount

&RS_Level2(&I).PSU_TASK_EFFORT.EFFORT_DT.Enabled = &TrueOrFalse;

&RS_Level2(&I).PSU_TASK_EFFORT.EFFORT_AMT.Enabled = &TrueOrFalse;

&RS_Level2(&I).PSU_TASK_EFFORT.CHARGE_BACK.Enabled = &TrueOrFalse;

End-For;

AFTER:

Local Boolean &TrueOrFalse = (PSU_TASK_RSRC.COMPLETED_FLAG.Value = "N");

For &I = 1 To &RS_Level2.ActiveRowCount

Local Record &TaskEffort = &RS_Level2(&I).PSU_TASK_EFFORT;

&TaskEffort.EFFORT_DT.Enabled = &TrueOrFalse;

&TaskEffort.EFFORT_AMT.Enabled = &TrueOrFalse;

&TaskEffort.CHARGE_BACK.Enabled = &TrueOrFalse;

End-For;

- saves a simple evaluation from happening three times ( &RS_Level2(&I).PSU_TASK_EFFORT)

- arguably cleaner looking code.

Another one.