A blog for developers programming with Autodesk platforms, particularly AutoCAD and Forge. With a special focus on AR/VR and IoT.

January 16, 2012

Profiling different methods for testing AutoCAD curves using .NET

A couple of recentposts have covered different ways of checking whether a point is on a curve. This post looks at a rudimentary way of profiling the relative performance of these various methods, in order to determine the best approach to use, in general.

There are various performance profiling technologies available on the market, but given the specificity of the cases we wanted to test and compare, I rolled my own very simple approach. Nothing at all earth-shattering, just a helper function that will call one of our methods (passed in as a delegate with a specific signature) a set number of times, and return the elapsed time (in seconds) for that set of operations.

For instance, we are typically calling our various functions 100K times, and in each set of calls using different combinations of object type, function and point (whether on or off the curve in question). The duration of each set of 100K operations gets returned to the calling function, which then saves the information for the various sets out to a CSV file for easy loading into Excel.

Each run of the POC command creates a new CSV file, so there is some manual work still involved to copy & paste into a central file to compare the results: this could also be automated, but the law of diminishing returns led me to draw the line here (it’s really a one-off investigation, not a regularly-run performance benchmark). You may choose to drawn the line elsewhere in your own, regularly-run performance analyses, of course.

A few things to bear in mind: our ProfileMethod() function checks for the elapsed time between the beginning and the end of the set of calls. Lots of things can contribute to the operations taking time, so it's worth running the various tests multiple times and comparing the results. That's certainly what I did before settling on the results at the bottom of this post.

Here’s the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using System.IO;

using System;

namespace CurveTesting

{

publicdelegateboolIsPointOnCurveDelegate(Curve cv, Point3d pt);

publicclassCommands

{

[CommandMethod("POC")]

publicvoid PointOnCurve()

{

Document doc =

Application.DocumentManager.MdiActiveDocument;

Database db = doc.Database;

Editor ed = doc.Editor;

PromptEntityOptions peo =

newPromptEntityOptions("\nSelect a curve");

peo.SetRejectMessage("Please select a curve");

peo.AddAllowedClass(typeof(Curve), false);

PromptEntityResult per = ed.GetEntity(peo);

if (per.Status != PromptStatus.OK)

return;

PromptPointResult ppr = ed.GetPoint("\nSelect a point");

if (ppr.Status != PromptStatus.OK)

return;

Transaction tr = db.TransactionManager.StartTransaction();

using (tr)

{

Curve cv =

tr.GetObject(per.ObjectId, OpenMode.ForRead) asCurve;

if (cv != null)

{

StreamWriter sw =

File.CreateText(GetFileName(cv));

sw.WriteLine(

"GCP (On),GCP (Off),GDAP (On),GDAP (Off),PL (On),PL (Off)"

);

// Let's repeat each method call 100K times

constint reps = 100000;

// We measure up to 6 sets of calls per object

// (6 for Polylines, 4 for other Curves)

// The calls with Point3d.Origin being passed as the

// point should result in the point being off the curve

// (unless the test data happens to have geometry

// going through the origin)

double d1, d2, d3, d4, d5, d6;

d1 =

ProfileMethod(

IsPointOnCurveGCP, cv, ppr.Value, reps

);

d2 =

ProfileMethod(

IsPointOnCurveGCP, cv, Point3d.Origin, reps

);

d3 =

ProfileMethod(

IsPointOnCurveGDAP, cv, ppr.Value, reps

);

d4 =

ProfileMethod(

IsPointOnCurveGDAP, cv, Point3d.Origin, reps

);

// For Polylines we have an additional method we can use

Polyline pl = cv asPolyline;

if (pl != null)

{

d5 =

ProfileMethod(

IsPointOnCurvePL, cv, ppr.Value, reps

);

d6 =

ProfileMethod(

IsPointOnCurvePL, cv, Point3d.Origin, reps

);

sw.WriteLine(

"{0},{1},{2},{3},{4},{5}", d1, d2, d3, d4, d5, d6

);

}

else

{

sw.WriteLine(

"{0},{1},{2},{3}", d1, d2, d3, d4

);

}

sw.Close();

}

tr.Commit();

}

}

// Return a unique filename in the root folder on C:\

privatestring GetFileName(Curve cv)

{

int i = 0;

string fname;

do

{

fname =

"c:\\results for " + cv.GetType().Name +

(i > 0 ? i.ToString() : "") + ".csv";

i++;

}

while (File.Exists(fname));

return fname;

}

// Run a specific function a certain number of times, returning

// the elapsed time in seconds

privatedouble ProfileMethod(

IsPointOnCurveDelegate fn, Curve cv, Point3d pt, int reps

)

{

DateTime start = DateTime.Now;

for (int i = 0; i < reps; i++)

{

fn(cv, pt);

}

TimeSpan elapsed = DateTime.Now - start;

return elapsed.TotalSeconds;

}

// A generalised IsPointOnCurve function that works on all

// types of Curve (including Polylines), but catches an

// Exception on failure

privatebool IsPointOnCurveGDAP(Curve cv, Point3d pt)

{

try

{

// Return true if operation succeeds

cv.GetDistAtPoint(pt);

returntrue;

}

catch { }

// Otherwise we return false

returnfalse;

}

// A generalised IsPointOnCurve function that works on all

// types of Curve (including Polylines), and checks the position

// of the returned point rather than relying on catching an

// exception

privatebool IsPointOnCurveGCP(Curve cv, Point3d pt)

{

try

{

// Return true if operation succeeds

Point3d p = cv.GetClosestPointTo(pt, false);

return (p - pt).Length <= Tolerance.Global.EqualPoint;

}

catch { }

// Otherwise we return false

returnfalse;

}

// A manual approach that works specifically for polylines

privatebool IsPointOnCurvePL(Curve cv, Point3d pt)

{

bool isOn = false;

Polyline pl = cv asPolyline;

if (pl != null)

{

for (int i = 0; i < pl.NumberOfVertices; i++)

{

Curve3d seg = null;

SegmentType segType = pl.GetSegmentType(i);

if (segType == SegmentType.Arc)

seg = pl.GetArcSegmentAt(i);

elseif (segType == SegmentType.Line)

seg = pl.GetLineSegmentAt(i);

if (seg != null)

{

isOn = seg.IsOn(pt);

if (isOn)

break;

}

}

}

return isOn;

}

}

}

Here’s a graph plotting the CSV file from various objects the POC command has been executed against:

Some observations:

As expected, the GCP (GetClosestPoint) approach is generally the most efficient, whether the point is on or off the curve.

The GDAP (GetDistAtPoint) approach is comparable – and in some cases cheaper, such as for circles – when the point is on the curve, but much, much worse when the point is not.

When the point is on the curve, the function returns and there’s no need for further operations to be performed (whereas with GCP there is a modest amount of vector arithmetic to be done per call).

The PL (Polyline-specific segment iteration & testing) approach is cheap when you pick a point on and early segment, but in all other cases is pretty expensive (and this will go up, the larger the size of the polyline).

All these operations are relatively expensive when performed on Splines, presumably from the need to call through to ASM (the Autodesk Shape Modeler component).

Let’s take a look at just the data for the GetClosestPoint implementation, the best overall approach to use for testing whether a point is on a curve:

Interestingly circles have quite a high relative cost. That said, I assume the first three are fixed, while polylines and splines will presumably get more expensive as the number of segments/control points increase: the test data used a 10-segment polyline and a spline with 7 control points – both of a modest size.

If anyone has additional thoughts on this analysis – whether you’ve seen similar behaviour or there’s something I’ve missed – please do post a comment.