Performance of Object Expressions

In a previous post I described how object expressions in JavaScript worked and compared them to using calls to resolveNode() and using FormCalc.  There is a trade off between readability and performance.  From the previous post:

“SOM parser evaluation is faster than JavaScript object evaluation.  If you have an expression that must be executed many, many times, then it is better to wrap it in resolveNode() or use FormCalc.  However, for most forms with modest amounts of script, the difference in overall form performance is negligible. Most of the time ease-of-scripting and code readability are more important.”

We were working on an internal prototype that made me want to quantify the difference in performance between object expressions and SOM expressions.  In order to measure performance I needed a form that would evaluate many, many expressions.
The result is a sample form that simulates John Conway’s famous Game of Life.

The Game of Life Form

A few notes about how the form works:

  • Each cell is a check box field.  You can re-arrange the starting pattern by toggling the fields
  • The grid is initialized by creating nested row/column subforms
  • The row and column subforms are renamed so that the object expressions don’t use indexes.
    e.g. grid.row[3].column[4] becomes grid.row_3.column_4

Most applications that simulate this game access the cells in nested loops.  For this sample, the loops are flattened so that we have very large scripts with hard coded object expressions.  For example, the code to find neighbours of the cell at (3,4) is:

vNeighbourCount = 0;
vNeighbourCount += form1.grid.row_2.column_3.cell.rawValue;
vNeighbourCount += form1.grid.row_3.column_3.cell.rawValue;
vNeighbourCount += form1.grid.row_4.column_3.cell.rawValue;
vNeighbourCount += form1.grid.row_2.column_4.cell.rawValue;
vNeighbourCount += form1.grid.row_4.column_4.cell.rawValue;
vNeighbourCount += form1.grid.row_2.column_5.cell.rawValue;
vNeighbourCount += form1.grid.row_3.column_5.cell.rawValue;
vNeighbourCount += form1.grid.row_4.column_5.cell.rawValue;
if (form1.grid.row_3.column_4.cell.rawValue)
  vNewState = (vNeighbourCount == 2 || vNeighbourCount == 3) ?1:0;
else
  vNewState = (vNeighbourCount == 3) ? 1:0;
form1.grid.row_3.column_4.cell.extras.newState.value = vNewState;

After one pass where we stored the new state for each of the cells in the extras of the cell field, we assign the new state:

form1.grid.row_3.column_4.cell.rawValue =
form1.grid.row_3.column_4.cell.extras.newState.value;

Repeat that code for each of the 2,500 cells in the grid and now the form has around 36,900 lines of JavaScript and 29,405 object references.  That should be good enough to test the performance of object expressions.

Then for comparison purposes, the form has another  script that does the same operation using calls to resolveNode():

vNeighbourCount=0;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_2.column_3.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_3.column_3.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_4.column_3.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_2.column_4.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_4.column_4.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_2.column_5.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_3.column_5.cell”).rawValue;
vNeighbourCount +=
  this.resolveNode(“form1.grid.row_4.column_5.cell”).rawValue;

if (this.resolveNode(“form1.grid.row_3.column_4.cell”).rawValue)
  vNewState = (vNeighbourCount == 2 || vNeighbourCount == 3) ?1:0;
else
  vNewState = (vNeighbourCount == 3) ? 1 : 0;
this.resolveNode
  (“form1.grid.row_3.column_4.cell.extras.newState”).value =
vNewState;

And one more comparison where we do the same in FormCalc:

vNeighbourCount = 0
vNeighbourCount = vNeighbourCount + form1.grid.row_2.column_3.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_3.column_3.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_4.column_3.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_2.column_4.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_4.column_4.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_2.column_5.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_3.column_5.cell
vNeighbourCount = vNeighbourCount + form1.grid.row_4.column_5.cell
if (form1.grid.row_3.column_4.cell) then
  vNewState = if(vNeighbourCount == 2 or vNeighbourCount == 3,1,0)
else
  vNewState = if(vNeighbourCount == 3, 1, 0)
endif
form1.grid.row_3.column_4.cell.extras.newState = vNewState;

Results

The good news is, that all three variations are *very* slow — great for measuring performance :-)

The performance results when I ran this on my laptop:

JS with Object Expressions JS with calls to resolveNode() FormCalc
Mlliseconds 7154 4673 1735
Milliseconds
per Expression
0.243 0.159 0.059

Clearly there are differences.  I was surprised at how much faster the FormCalc version was.  I would like to understand that better some day.

But now before you run off and re-code your forms to use resolveNode() or to use FormCalc, we have to put the numbers into perspective. For the slowest variation (object expressions in JavaScript), we evaluated 29,405 expressions in just over 7 seconds.  That means each expression evaluated in roughly 0.24 milliseconds.  I’m going to go out on a limb here and assume that this is fast enough for most forms.  Sure, the FormCalc version does it in  0.06 milliseconds, but your end users will not notice the difference.  Unless, of course, your form is doing something frivolous such as simulating the Game of Life.  The test validated the original assumption: in most cases code readability and maintainability trump any performance issues.

Something Useful

So far this blog post has not uncovered anything particularly useful, and I feel obliged to leave you with something that you can use in your day-to-day form design.

In the previous post that dealt with object expressions, I described how the JavaScript engines deal with “naked field references”.  There is a nuance that you should be aware of:  the JavaScript engines treat a variable with an explicit declaration differently from a variable without a declaration. i.e.

var foo = “hello world”;
xfa.host.messageBox(foo);

is handled differently from

foo = “hello world”;
xfa.host.messageBox(foo);

In the first case, the JavaScript processor will ask the XFA engine if it recognizes “foo”.  The XFA engine says “no” and from that point on, the JavaScript processor will not ask again when it encounters more references to “foo”.  In the second case where the “var” keyword is not used, the JavaScript processor asks the XFA engine for a definition of “foo” each time it encounters “foo” in the script.

Now consider what happens when you code:

for (var i=0; i<10000; i++)
  total += po.nodes.item(i).subtotal.rawValue;

vs.

for (i=0; i<10000; i++)
total += po.nodes.item(i).subtotal.rawValue;

In the first case, the XFA processor evaluates “i” once.  In the second case, the XFA processor evaluates “i” 30,000 times.  Would you notice the difference?  It depends on two factors:

  1. How many iterations in the loop and how many references to the loop counter
  2. The cost of one lookup – how many objects are in scope when we do the evaluation.  When the XFA processor searches for “i”, it does a physical scan through all objects that are within scope of the current context.

The Deep End

If you are writing script for a form that will run on the server, there is a bug you might want to be aware of.  As mentioned, once the JavaScript engine determines that an identifier is a variable or an XFA object, it will not ask again.  On the client this happens on a per-script basis.  However on the server this happens on a form-wide basis.  e.g. if an initialization script uses “foo” as a variable, then it will be assumed to be a variable in all other scripts on the form.  Another reason why my preference is to avoid using common names for variables.  For reliability — and for readability I prefer to prefix my variables e.g. “vIndex”.