Performance Tests (MAXScript)

From Tech Artists Wiki

Jump to: navigation, search

Some MAXScript performance metrics.


Test Setup

The tests provided are all run in the following context, unless otherwise specified (number of iterations or different loop types):

t1 = timeStamp()
for i = 1 to 2000000 do --however many iterations
format "% ms\n" (timeStamp() - t1)

Help File- How to Make it Faster?

The following suggestions are provided in the MXS Help under "How To-> Writing Better and Faster Scripts -> How To Make It Faster?" For more information, please see the MXS help file. Some of these topics will also be touched upon/elaborated/rehashed in this article. Some of the best optimization info can be found in the Help file- read it and re-read it until it is memorized.

  • Disable Viewport Redraws when making changes to scene objects
  • Disable Undo system when possible
  • Modify Panel can be slow – change to Create Panel when possible
  • Use the 'flagForeground' node viewport state method
  • Never get a single pixel when you can get a whole line
  • Use Mapped Functions instead of For loops when possible
  • Only calculate once if possible
  • Cache freqeuntly used functions and objects
  • Dice your data into smaller pieces
  • Use bitArrays instead of Arrays when possible
  • Pre-initialize arrays when final size is known
  • Recursive functions can be faster
  • matchPattern is faster than findString
  • Do not use return, break, exit or continue
  • Use StringStream to build large strings
  • Use name values instead of strings when possible
  • Try not to use Execute function if there is an alternative
  • For Loop By Index Vs For Loop Through Collection Performance
  • Optimizing Particle Flow Script Operator For Loops For Speed

How to Make it Faster?

Getting Data

Accessing object properties

Accessing an object's properties take time! Given a sphere:

  • 890 ms to access .transform
  • 1150 ms to access .wirecolor
  • 1766 ms to access .pos
  • 2062 ms to access .rotation
  • 3480 ms to access .scale
  • 1500 ms to access primitive properties (.radius, .smooth, etc.)

A word also about Parameters and custom attributes. If we add a custom float attribute (.cust) to the Sphere:

  • 7234 ms to access .cust!

Other speeds remain unchanged. Accessing the Parameters of Scripted Plugins also incurs an overhead. Accessing a float parameter .theParam of a Box plugin took:

  • 2344 ms

Much faster than a Custom Attribute but still slower than the primitive properties.

If you are going to be referring to properties often, it is often worthwhile to make references to the properties. At what point you should create a reference is discussed in the section "When to access and when to assign a reference."

When to access and when to assign a reference

For these tests, we are executing 1,000,000 iterations.

Creating a reference to a property or method has some overhead associated with it. At what point does it become better to assign a variable to the prop/object or to just call it outright?

First we can time how long it takes to access properties.

  • 734 ms for 1 access.
  • 1375 ms for 2 accesses.
  • 1766 ms for 3 accesses.
  • 2203 ms for 4 accesses.

Each access seems to cost 400-500 ms. We can run the same test by assigning variables (b = a.radius; c = a.radius; ...).

  • 969 ms for 1 assignment.
  • 1844 ms for 2 assignments.
  • 2485 ms for 3 assignments.
  • 3156 ms for 4 assignments.

Each assignment costs about 700 ms. Subtract from this the 400-500 ms cost simply for the access, and the assignment doesn't become too costly.

Creating a steadfast rule or criteria is probably not worth it- code readability is usually more important than a couple milliseconds, unless you are running a very performance-sensitive script. However, the following rules of thumb can probably be applied:

  • For properties with fast access time (.transform), you can probably call it 3 or 4 times without being quicker to make a reference.
  • For Parameters and more costly lookups (.scale, for example), it is probably faster to assign a reference if using 3 or so times.
  • For Custom Attributes, create a reference if using the property more than once.

Array reads

For these tests, we are using 2 million iterations, and a is an array of 1000 floats, from 1.0 to 1000.0.

Array reads are much faster than property lookups. For example:

  • 720 ms for 1 read.
  • 1407 ms for 2 reads.
  • 1578 ms for 3 reads.
  • 1672 ms for 4 reads.

The decrease in time from read-to-read is probably due to the array being in memory and easier for MXS to access. However, even a 700ms-per-read is much faster than the 890ms-per-access for the fastest returning object property, the transform; and it is 2 or 3 times as fast as accessing other properties (and 10 times faster than accessing a CA)! Furthermore, the lookup time of a 10 member array is the same as the lookup time as a 5000 member array.

That said, storing values in arrays (as globals, locals, or in structs) is probably not a good idea, since it can get very confusing. As always, code readability is better than a couple millisecond gain in all but the most performance-sensitive programs.

Setting Data

Since setting is always much slower than getting, we'll be using 100,000 iterations unless noted. We'll also always be using references to numbers instead of numbers themselves in the loop, to avoid penalties with constructing a Matrix3 or Point3 value for each iteration (the difference is significantly faster for multi-dimensional numbers and shows not speed change for integers/floats).

Setting object properties

Setting properties sees different results than getting properties, very much tied to the type of value we are setting. Accessing an object's properties take time! Given a sphere:

  • 1750 ms to set .transform
  • 80 ms to set .wirecolor
  • 1032 ms to set .pos
  • 1234 to set .rotation
  • 656 ms to set .scale
  • 860 ms to set primitive properties (.radius, .smooth, etc.)
  • 1672 ms to set the Custom Attribute .cust
  • 563 ms to set the Parameter .theParam

These results are a bit interesting. They do not really correlate with previous 'get' investigations, or with data complexity. As a rule of thumb, just set properties as infrequently as possible; setting j = 1.2 in our loop took just 79 ms, so make sure to use intermediate variables to store values instead of constantly setting them.

Writing arrays

Writing arrays is relatively fast, so we are back up to 2 million iterations.

  • 1125 ms for 1 write.
  • 2141 ms for 2 writes.
  • 2985 ms for 3 writes.
  • 3719 ms for 4 writes.

Increased writes so a much more linear increase in time than reading from arrays, which nearly leveled after a couple reads. Array size and the setting index do not seem to have any effect on time taken.

There are a few ways to create and edit arrays outside of setting items explicitly.

  • append is very fast. To append 100,000 integers to an empty array takes only 80-90 ms.
    • Compare to using collect to create an array, which takes about 60 ms for the same number.
  • appendIfUnique is very slow. To appendIfUnique 10,000 integers takes 720 ms. This gets exponentially faster and slower with more items to test against (100,000 integers takes 69766 ms while 1000 integers takes 8 ms).
    • Both append and appendIfUnique have a memory overhead- appending very large arrays will require more memory.
  • Pre-initializing the array size as per the help file suggestion takes slightly longer but will save memory (which outside of testing environments is probably more important).
    • appending a 1,000,000 member array takes 720 ms.
    • Initializing an array to 1,000,000 items long and setting all items by index takes 906 ms.
    • Initializing an empty array and then setting all items by index (no pre-initialization to the correct size) takes 920 ms, just slightly longer on average.


Functions carry with them some unavoidable overhead. Discovering where this overhead is can help a coder optimize.

Calling Functions

We get the following overhead from calling empty functions (2 million iterations):

  • 594 ms for 0 calls.
  • 1484 ms for 1 call.
  • 3031 ms for 2 calls.
  • 3938 ms for 3 calls.
  • 4875 ms for 4 calls.

We get the following for calling functions which call another function:

  • 2563 ms for 2 calls (one call that calls another).
  • 3375 ms for 3 calls (one call that calls another that calls another).
  • 4360 ms for 4 calls (one call that calls another that calls another that calls another).

Passing parameters

Is it faster to pass more an object and access its properties in the function, or faster to access its properties and pass those to a function; what impact do number, and complexity, of the arguments take; is there an overhead to optional parameters? The tests below have 1 million iterations.

  • 2953 ms to pass an object (sphere) as the only parameter into a function that accesses its .radius, .pos, .transform, and .wirecolor properties.
  • 2563 ms to access those properties when we call the function and use them as parameters/arguments (the function does nothing).
  • 760 ms to pass 1 or 2 arguments (any type: number types, strings and arrays of various length, objects) into a function.
  • 780 ms to pass 4 arguments of any type into a function.
  • 805 ms to pass 6 arguments of any type into a function.

We see a very slight, but consistent, increase for number of arguments passed into a function.


Performance of operators, including math and comparisons. 5 million iterations. Also note that many of these are rounded averages, each timing was performed about 10 times. If there are differences in timing reported, it was because of sustained differences.


The value returned (true or false) made timing irrelevant. All times are comparing two integers.

  • 2500 ms for ==.
  • 2300 ms for !=.
  • 2300 ms for >=.
  • 2400 ms for <=.
  • 2500 ms for >.
  • 2500 ms for <.

It is possible that all these values are the same, they are so close to each other. I cannot think of a good reason for discrepancies, but this is also over 5 million iterations. More testing is needed to determine anything absolute or conclusive.

The following tests use an if/then loop that executes nothing.

  • 3300 ms for true == true (the same results for true == false, the value returned was irrelevant).
  • 3300 ms for true != true.
  • 2300 ms for true.
  • 3500 ms for not false.

Given this, try to avoid val == true where possible, and replace only with val'. An equality comparison (val == false) is faster than not val, but only marginally.


  • 2300 ms to just access to float or integer variables (a, which equals eleven; and b, which equals twenty-three).
  • 2500 ms for a + b where a and b are floats.
  • 2500+ ms for a - b where a and b are floats (consistently just slightly slower than addition).
  • 2500 ms for a * b where a and b are floats.
  • 2600+ ms for a / b where a and b are floats.
  • The results were the same where a and b are integers, except in the case of addition slightly longer.
  • 4300 ms to negate a and b (-a; -b).
  • 4600 ms to multiply a and b each by c (c equals -1).


Tests on whether it is faster to normalize vectors that may be normalized, or check if it is normalized first (length == 1.0). v1 = [1,0,0] and v2 = [1,1,1]. 5 million iterations.

  • 2460 ms for normalize v1.
  • 2672 ms for normalize v2.
  • 2671 ms for length v1.
  • 2750 ms for length v2.

Grabbing the length takes about as long as normalization, so just normalize if there is any question, especially since it is quite fast.


Tests whether it is faster to convert a string to a maxscript value via execute, or other ways. 300,000 iterations.

  • 2740 ms for execute "#{1..10}"
  • 2530 ms for readvalue ("#{1..10}" as stringstream)
  • 4230 ms for execute (("[1.000000000,1.000000000,1.000000000,1.000000000]"))
  • 3937 ms for readvalue ("[1.000000000,1.000000000,1.000000000,1.000000000]" as stringstream)

So it turns out the conversion to stringstream with readvalue is faster than using execute. Longer strings/expressions will need to be tested as well, such as a rollout.

Personal tools