4X Variation in Execution time of rs.EvaluateSurface

I see 13 sec to an agonizing 55 sec execution time for the script below when max_v in def problem is set to 700. The first time it often runs in 13 sec but the next time it takes 55 sec. How can this be!!! What do I need to do in order to consistently get the faster time? I did some tests:
max_v time
100 2.6 sec
200 3.6 sec to 7.6 sec
400 8 sec to 21 sec
700 13 sec to 55 sec
The display is disabled for all these tests. Is there some kind of garbage collection going on for the larger sizes of the 2D matrix variable? 100k entries seems mostly reproducible but 200k to 700k can increase by 400% in execution time.

Please try this simple script yourself and let me know your results. Set max_v to a value and try multiple executions. Are the times consistent? Is a graph of time vs max_v reasonably linear?

import rhinoscriptsyntax as rs
from Rhino.Geometry import Point3d
from time import time
P3d = Point3d
def problem(srf):
	time1 = time()
	max_u = 1070.; max_v = 700.
	a = []
	for i in range(int(max_u)):
		b = []
		for j in range(int(max_v)): b.append(rs.EvaluateSurface(srf, i, j))
		a.append(b)
	print 'Time to fill matrix = ', time() - time1
	return()
def makeBase():
	pts = [P3d(0,0,942),P3d(0,88,953),   P3d(0,175,964),   P3d(0,263,973),   P3d(0,350,983),   P3d(0,438,991),   P3d(0,525,986),   P3d(0,613,991),   P3d(0,700,1000),
		P3d(220,0,951), P3d(220,88,969), P3d(220,175,976), P3d(220,263,988), P3d(220,350,995), P3d(220,438,1000),P3d(220,525,1000),P3d(220,613,1002),P3d(220,700,999),
		P3d(267,0,959), P3d(267,88,969), P3d(267,175,977), P3d(267,263,982), P3d(267,350,994), P3d(267,438,998), P3d(267,525,1000),P3d(267,613,1002),P3d(267,700,987),
		P3d(535,0,934), P3d(535,88,945), P3d(535,175,955), P3d(535,263,963), P3d(535,350,971), P3d(535,438,962), P3d(535,525,967), P3d(535,613,975), P3d(535,700,961),
		P3d(802,0,890), P3d(802,88,906), P3d(802,175,916), P3d(802,263,922), P3d(802,350,912), P3d(802,438,913), P3d(802,525,912), P3d(802,613,929), P3d(802,700,902),
		P3d(1070,0,857),P3d(1070,88,876),P3d(1070,175,874),P3d(1070,263,865),P3d(1070,350,835),P3d(1070,438,873),P3d(1070,525,835),P3d(1070,613,853),P3d(1070,700,855)]
	srf = rs.AddSrfPtGrid([6,9], pts, [3,3], [False, False])
	return(srf)
# Clear document
rs.EnableRedraw(False)
rs.DeleteObjects(rs.AllObjects())
srf = makeBase()
problem(srf)
rs.EnableRedraw(True)

Moved to Serengeti > Developer

Hi @Terry_Chappell,

I just ran your script 8 consecutive times from the Rhino 6 Python script editor. Here are the results:

Time to fill matrix =  9.72915649414
Time to fill matrix =  4.58641052246
Time to fill matrix =  4.63462066650
Time to fill matrix =  4.59175872803
Time to fill matrix =  4.60178375244
Time to fill matrix =  4.59635925293
Time to fill matrix =  4.60143280029
Time to fill matrix =  4.57709503174

Does this help?

– Dale

Do you have any idea what causes the 2X time variation you see? I see much larger values. When I extract the timing of some of the functions called internal to rs.EvaluateSurface in my full Python script (which calls it 800,000 time), I find:

Breakdown of times for a bad result of 87.1 sec
EvalSurf = 87.1
coercesurface= 66.9
___isinstance RhinoGeometry.Surface = 4.6
___type is RhinoDocObject.ObjRef = 5.7
___coerceguid = 13.4
___scriptcontext.doc.Objects.Find = 7.
___isinstance Rhino.Geometry.Surface = 4.2
___isinstance Rhino.Geometry.Brep = 4.6
surface.PointAt = 4.2

And a good result of 28.5 sec:
EvalSurf = 28.5
coercesurface= 21.8
___isinstance RhinoGeometry.Surface = 1.9
___type is RhinoDocObject.ObjRef = 2.3
___coerceguid = 3.6
___scriptcontext.doc.Objects.Find = 2.3
___isinstance Rhino.Geometry.Surface = 1.9
___isinstance Rhino.Geometry.Brep = 2.1
surface.PointAt = 1.9

The worst offender is coerceguid which is 3.7X slower in the bad run although the total time is 3.1X slower. But all the functions are slowing down considerably.

Any idea what is behind this? My memory is at 50% or less during the run. The Windows Task Manager shows CPU usage at 25% for both runs and there are no other tasks running that take 5% of CPU usage and there are few other tasks running.

I could not stand the slowdown so I kept only 3 lines:
srfObj = doc.Objects.Find(srf)
surface = srfObj.Geometry.Faces[0]
pt = surface.PointAt(u,v)
This runs in 2 sec or so for the 800,000 calls. The cost is that the code is not very robust. The good thing is that I generate the surface just before I evaluate it so I know it is valid.

Regards,
Terry.

Regards,
Terry.

Hi @Terry_Chappell,

If you are concerned about performance, then calling rhinoscriptsyntax methods is probably not the correct approach, as many of these methods are inefficient.

For example, here is the code behind rhinoscryptsyntax.EvaluateSurface:

def EvaluateSurface(surface_id, u, v):
    "Evaluates a surface at a U,V parameter"
    surface = rhutil.coercesurface(surface_id, True)
    rc = surface.PointAt(u,v)
    if rc.IsValid: return rc
    return scriptcontext.errorhandler()

The rhutil.coercesurface method tries to find the object in the document based on it’s id. Once you have the object, here is no reason to search for it again. But that’s what every call to rhinoscryptsyntax.EvaluateSurface does.

See if this verison of your problem function doesn’t work faster:

def problem(surface_id):
    time1 = time()
    surface = rs.coercesurface(surface_id, True)
    if surface:
        max_u = 1070.; max_v = 700.
        a = []
        for i in range(int(max_u)):
            b = []
            for j in range(int(max_v)): 
                b.append(surface.PointAt(i, j))
            a.append(b)
        print 'Time to fill matrix = ', time() - time1
        return

– Dale

Yes this is over 10X faster. It is what I am already doing. I just replaced coercesurface with two lines from inside it and moved it outside the loop like you did. Great minds …

Is there a way to write faster scripts in general? What should I use instead of rhinoscriptsyntax calls?

Regards,
Terry.

If you are concerned about performance, then calling RhinoCommon directly, in most cases, is probably preferred.

– Dale

Are you calling RhinoCommon the collection of procedures shown to the left of the Python script editor? There, for rhinoscriptsyntax, I find details on rs.EvaluateSurface and hundreds of others. There, under the list of Rhino items, I find PointAt under Surface along with hundreds of others.

Or is RhinoCommon a separate repository of procedures?

Regards,
Terry.

Rhinoscriptsyntax is simply a set of “wrapper” functions that call RhinoCommon methods.

If you look here

C:\Users\<username>\AppData\Roaming\McNeel\Rhinoceros\5.0\Plug-ins\IronPython (814d908a-e25c-493d-97e9-ee3861957f49)\settings\lib\rhinoscript

you will find the entire library, if you look inside one of the files with a text editor, you will see how they work.

If I understand correctly, RhinoCommon itself is also a sort of “wrapper” for calling Rhino’s core C++ programming, the advantage is that RhinoCommon methods can be called via Python, C#, VB, etc. - which facilitates programming plug-ins, etc.

–Mitch

In addition to what Mitch said:

Keeping the Rhino developer docs and the RhinoCommon API bookmarked, is super handy when getting going with this (although it admittedly can be a bit daunting at first).

1 Like

Yep, definitely… I didn’t post the links because I think I already sent them to Terry in a previous conversation…

–Mitch

1 Like

Ok, all good. I am already looking in the location Mitch pointed out. So going forward all my scripts should run in zero time (or maybe 10-100X faster than using rhinoscriptsyntax alone).

Thanks all.

Regards,
Terry.