VectorScript Performance

From Vectorlab
Jump to: navigation, search

A discussion on performance considerations in VectorScript, by Charles

Introduction

"Good performance" is going to require something different in every circumstance. Sometimes what speeds up one script will slow down another. The fine-grain detail has to do with exactly how many times you do something, and how expensive the calls are. Nevertheless, there are some general guidelines.

Dynamic Arrays

Re-allocating a dynamic array forces the creation of a new array of different dimensions, copying the contents of the old array into the new one, and then cleaning up the old one. If this is happening thousands of times in a script, you can see a hit because of it. So try to re-allocate as little as possible. If you know you'll need 1000 elements, allocate the array for 1200, and later, if you find you need more, then re-allocate in big blocks (such as 200 at a time). Don't re-allocate the array every time you add an element.

The following example re-allocates the dynarray in blocks of 200. (Of course, since the array is being loaded by ForEachObject, you could have simply used the Count function to get the number of items matching the criterion, and then used that number to allocate the array. So shoot me for not dreaming up a more realistic example. Nevertheless, the example illustrates a standard approach to dynarray allocation.)

PROCEDURE Example;
VAR
   handleArray :DYNARRAY [] of HANDLE;
   handleArrayCount :INTEGER;
   handleArrayAlloc :INTEGER;

PROCEDURE AddToHandleArray(h :HANDLE);
BEGIN
   handleArrayCount := handleArrayCount + 1;
   IF handleArrayCount > handleArrayAlloc THEN BEGIN
      handleArrayAlloc := handleArrayCount + 200;
      ALLOCATE handleArray [1..handleArrayAlloc];
   END;
   handleArray[handleArrayCount] := h;
END;

BEGIN
   handleArrayCount := 0;
   handleArrayAlloc := 0;
   ForEachObject(AddToHandleArray, '(SEL)');
END;
RUN(Example);

ResetObject

In a script that operates on PIOs in the drawing, don't reset the other objects if you don't have to. You might be doing SetRFields to poke new data in, but does that really require also calling ResetObject? (Sometimes it's just data that will appear in a worksheet somewhere, and there is actually no reason to reset the object.) Also, sometimes it makes a lot of sense to check the existing values before poking the new values in, and if the values aren't actually changing, then there's no need for a reset. You can save a lot of execution time this way.

Construct Geometry

As a general rule, math is much faster (at run-time) than construct geometry (though it's a tad slower at design-time). :) Creating and destroying objects involves a substantial amount of overhead, as VW has to do many things to preserve the integrity of the drawing list as objects are created and destroyed. If you can do everything that you need to do within VectorScript, you won't kick off all of those routines under the hood. And if you do need to create a construct object for some reason, it's better to re-use it than to delete it and create a new one next time. Editing is cheaper than creating and destroying.

Global Searches

ForEachObjectInList is very fast, and ForEachObject isn't bad, though you probably shouldn't use these more than once in a script if it can be avoided. These will step through every object (in the specified list, or in the entire drawing, respectively). Always run something like this once, to build up an array of handles, and then process the array of handles as many times as you want, rather than re-running the same search. There is more that could be said on this topic, and there are cases where ForEachObject can actually be faster than stepping through an array in VectorScript looking for a specific element. It all depends on how many objects are in the drawing, and how much data you might have to collect to support your searching. If you're really having performance problems, you might have to try it a couple different ways.

LOC Criterion

Instead of using the LOC criterion, you could build an array of the objects that you need to operate on at the beginning of the script. Instead of just storing the handles, you would also store the insertion points of the PIOs. Use GetSymLoc to find the insertion points, and store the x/y in additional elements within the TYPE structure. Then, to find an object that you want, instead of using the LOC criterion, you use the Norm function to test the distance of the object from a test point.

Benchmarking

You can use GetTickCount to capture the number of 60ths of a second since VW was launched. The basic strategy is simply like this:

tick1 := GetTickCount;
{do some stuff}
tick2 := GetTickCount;
{do some stuff}
tick3 := GetTickCount;

WriteLn('tick1: ', tick1 / 60);
WriteLn('tick2: ', (tick2 - tick1) / 60);
WriteLn('tick3: ', (tick3 - tick2) / 60);

Don't dump your performance logging to the text file during the script operation, or you'll pick up a performance hit just because of the file i/o (which by the way is kinda slow too).