Improving OpScript Performance

Understand the Lua Garbage Collector

In Lua, memory can be allocated on either the stack or the heap. Objects or memory allocated on the heap is reclaimed through Lua’s garbage collector. When the garbage collector is running your Lua code cannot make forward progress therefore, reducing the frequency with which the garbage collector must run or, the amount of garbage it must collect, will help to improve the performance of your OpScripts.

The following Lua constructs, while not bad, result in a new object being created which must be subsequently cleaned up during garbage collection. You should pay particular attention it these constructs are used within loops.

string_one..string_two - String concatenation or any string creation function could potentially result in the creation of a new object. As Lua strings are unique it will first check to determine if the string has been created elsewhere.

{ ... } - Each time a table constructor is executed, a new table is created.

function() ... end - Executing a function statement creates a closure. If this executed within a loop of n iterations it will result in the creation of n closures.

Summary

Understand which constructs create heap based object in Lua and be careful of their use in loops. If necessary, consider the use of object pools to reuse objects. For more information see http://lua-users.org/wiki/OptimisingGarbageCollection.

Avoid ”..” in loops

As a concrete example of Understand the Lua Garbage Collector, in this recommendation we explore the use of the string concatenation operator .., commonly used in OpScripts due to its convenient shorthand notation.

As an example, consider the following code used to build an L-System description:

local result = ""
for i = 1, #inputStr do
  local char = inputStr:sub(i,i)
  local ruleStr = rulesTable[char]
  if ruleStr then
    result = result..ruleStr
  else
    result = result..char
  end
end
return result

result is appended to in a tight loop. Whilst convenient, this results in a large number of string allocations and poor performance. The preceding code can be re-written to handle all concatenation only once the loop has completed as shown below:

local buf = {}
for i = 1, #inputStr do
  local char = inputStr:sub(i,i)
  local ruleStr = rulesTable[char]
  if ruleStr then
    buf[#buf+1] = ruleStr
  else
    buf[#buf+1] = char
  end
end
return table.concat(buf)

Running the above example as part of a large Katana scene file reduced the scene processing time of this function by approximately 2.5x as shown in the following table:

String Method Time
.. Operator 7.011s
Table.concat() 2.681s

Summary

When optimizing OpScripts, first identify the use of string creation and concatenation patterns, particularly within tight loops.