Performance Best Practices for Using Loops, Methods and Strings

You must understand and consider the performance impacts of your design decisions when you use Groovy scripting to configure the objects.

Let's look into the following concepts:
  • Loops
  • Methods
  • Strings

Loops

The ability of looping constructs (like while and for) to perform a set of operations repeatedly with minimal code is a very powerful feature. Used incorrectly, loops can trigger significant performance issues.

Looping Over View Result Sets

Take care when looping over the rows in a view's result set. When it's necessary to loop over a view's result set, use view criteria to limit the scope of the view's query as much as possible. It might be tempting to fetch all the rows and then decide which rows you actually need in your script. Querying and fetching rows you don't need can have a significant performance impact on your application.

Execute Loops Conditionally Where Appropriate

If there are conditions that should logically be satisfied before a loop is executed, enforce those prerequisites by enclosing the entire loop in one or more "if" clauses. If you don't, your application will do unnecessary work that slows it down.

Limit Loops to the Fewest Possible Iterations

There's a cost associated with each loop iteration; you'll never go wrong by assuming that each iteration is expensive. Every time you implement a loop, stop and ask yourself these questions:
  • If you're iterating over a view's result set, have you limited the result set to smallest possible number of rows that satisfy your business requirement?
  • Is it possible to reduce the number of iterations? For example, if you're searching for a particular value, exit the loop when the value is found instead of completing the remaining iterations. But don't use this technique as an excuse to avoid limiting the scope of your query using view criteria.

Methods

Assume Methods Are Expensive to Execute

Any time your code executes a method like newView, getAttribute, or any other method that you didn't implement, assume the worst even if the cost of executing the method appears to be negligible:
  • A method's performance characteristics could change due to various factors.
  • A method's execution cost is magnified by repeated execution inside a loop, in an attribute-level script, and similar contexts.
  • A method might execute rapidly but might consume some limited resource that impacts application scalability such as shared memory or database connections.
Don't be lulled into thinking that any method call is free (or nearly so). For example, there's always a cost associated with calling getAttribute to get an attribute value from a row – the cost might actually be quite high depending on the attribute's implementation. Any time you implement code that calls any method, you need to think about context:
  • Inside a loop, would the code work just as well if you executed the method outside of the loop? For example, if the method can return a different value every time the loop iterates, the method must be called inside the loop. If the method will always return the same value, it should be called before the loop is entered.
  • Whether inside or outside of a loop, should the method be called every time the script encounters the method? Or is the result of executing the method only useful if certain conditions are met? If so, the method should be placed inside an "if" clause that prevents the method's execution unless the pre-requisite conditions are met.

Consider the following two code examples. The first version always updates the LineEditedCount attribute. The second version only performs the update if the new value is different from the original value. Setting an attribute value might seem like a trivial operation. However, there's always a cost associated with setting an attribute. Some attributes are more costly to set than others – for example an attribute that triggers complex business logic when set. In addition, setting an attribute's value marks the row dirty, triggers row validation and adds the row to the pending transaction for commit – even if the new value is the same as the original value.

Unconditional setAttribute Call

setAttribute('Priority',newPriority)

Conditional setAttribute Call

// Only assign the value if it's not already what we want it to be

if (Priority != newPriority) {
setAttribute('Priority', newPriority)
}

Check Current Attribute Value before Setting the Attribute

Don't call a row's setAttribute method if the new value is the same as the current value. Failing to check if the setAttribute call is necessary can lead to the following issues:
  • Performance impact – calling setAttribute marks the row as dirty - even if setAttribute doesn't change the attribute's value. That's if the current value of the attribute is five and you call setAttribute(<attribute name>, five), the row is still marked dirty.
    • Dirty rows are added to the transaction and are subject to validation.
    • Dirty rows are posted to the database and committed when saving changes.

Validating and posting rows unnecessarily can negatively impact performance – especially if you fetch and update more rows than are required for your use case.

The following pseudo code example verifies that the proposed new value is different than the existing value before calling the setAttribute method:

if (firstName_c != value) { setAttribute('firstName_c',value)}

Cache Attribute Values

There's always a cost for retrieving an attribute value from an object. The cost varies by attribute depending on specific features implemented for that attribute. If you need to use the same attribute value more than once, call getAttribute once and cache the result in a variable. Don't call getAttribute for the same value multiple times.

Avoid Performing Expensive Operations in Validation Scripts

You should only use Validation scripts for validation; for example, you should not set attribute values in a validation script - Validation scripts might be called multiple times before an Object is finally committed. When performing any potentially expensive operation in a validation script, consider whether the operation needs to be included in a validation script or can be moved to a less often evaluated script.

Avoid Explicit Calls to Validate the Groovy Script

Validating a row always incurs a cost which varies by object based on the validations implemented for that object. Explicitly calling validate should not be necessary. If you call validate explicitly, you're most likely adding more validation cycles beyond what would otherwise be required.

Variable Initialization

A variable initialization can be as simple as def count = 1.

You can initialize variables with simple scalar variables like this anywhere that satisfies the business requirements without worrying about performance implications. This topic covers variable initializations that require more expensive operations such as a method that performs complex operations and / or consumes shared resources.

The following examples illustrate different ways to initialize a variable including best practices that apply to all variable initializations even when there isn't a specific performance concern. The same best practices that help maximize your application's performance also serve to clarify how and when each variable is used; this tends to result in more reliable code that's easier to maintain.
  • Initializes changeVO on every iteration of loop whether or not it's actually used; this is inefficient and will trigger errors if the code creates too many view instances.
  • Initializes the changeVO before entering the loop. Because the changeVO query doesn't change from one iteration to the next, this is much more efficient. This does mean that changeVO is created even if the condition.
  • Initializes changeVO by calling newView the first time that the specified condition is met. This is the most efficient version of this code. This code creates one changeVO instance at most and none if the condition requiring its use isn'tmet.

Strings

Strings are immutable; they can't be changed after they're created. When you concatenate two strings, a new String object is created to hold the combined Strings – creating new objects takes time and results in more work for the garbage collector. In the following example, notice the use of single quotes to define a String literal.

//Creates new String object to hold String literal plus an expression result
def receivedMessages = 20
def readMessages = 10
def confirmation = 'You have ' +
(receivedMessages – readMessges) + ' unread message(s)'

Groovy offers a more efficient choice to string concatenation. This option is substituting string using GStrings. A GString is created by using double quotes instead of single quotes.

// Double quoted string with substitution expressions creates a GString

def confirmation = "You have ${receivedMessages - readMessages} unread message(s)"

println(confirmation) //final String created here

GStrings offer two potential performance benefits as compared to standard String concatenation.
  • A GString stores the component parts of the String and defers String creation until the String is actually needed.
  • A GString doesn't evaluate component expressions - ${…} until the String is created. Expressions aren't limited to simple arithmetic as in the example above; you can include blocks of Groovy code (you must ensure that the final statement returns the value that you want to include in the String).

Looking back at the first example of String concatenation, the expression receivedMessages – readMessages is evaluated and the final String is created when the variable confirmation is set. In the second example using a GString, both expression evaluation and final String creation are deferred until the println statement is executed. If you're defining a literal String with no substitution expressions, it's slightly more efficient to use single-quotes.

Use GStrings and single quotes as appropriate when writing new code – it's very difficult to identify up front how much benefit (if any) a particular implementation will derive from string optimization. But there's no added cost for implementing optimized Strings so the best practice is to hedge your bets and implement optimized Strings from the start.

Move cautiously and temper your expectations if considering a retrofit of existing code. Every code change carries some level of risk. String optimization alone might not produce significant (or even measurable) performance improvement in many cases. If your configurations suffer from poor performance, you should focus first and foremost on the other best practices discussed in this document.