Rhino 6 Beta > 20X slower than Rhino 5. But now fixed and is 100X faster

I have a Python script that colors a mesh with 544458 vertices according to the Z of its vertices.
The first time I execute the script in Rhino 6 Beta it says:
Time to color mesh = 76.2056427002 sec
which I thought (before checking Rhino 5) is not too bad for a half-million plus mesh.

Then I found out that if I repeat the same exact script with no changes, it can say:
Time to color mesh = 980.461730957
Can you imagine what it is like to wait over 16 minutes for something that really only takes 16 sec more than 1 minute? But wait it gets worse. If you do this in Rhino 5 you only have to wait 29 sec. See below.

If your expectations were set by Rhino 5:
Typical Rhino 5 times (mostly reproducible, at least within 1.5X)
544458 vertices in mesh.
Time to import .obj file = 12.5874710083
Time to color mesh = 29.3717498779 sec

Then you are going to want to jump out the window when you see the Rhino 6 Beta result:
The best Rhino 6 Beta Times:
544458 vertices in mesh.
Time to import .obj file = 11.9335174561
Time to color mesh = 980.461730957 sec (can be as small as 76 sec, just to drive you crazy)

This slow down and variability I traced to the use of rs.EvaluateSurface. If I do not use this function, the variability can be small. I also suspect other rs. functions are also 10-20X slower.

I extracted a very small subset of the script which illustrates this behavior. You will find it below. Just execute it repeatedly and you should see up to a 4X variation in execution time if your machine is like my machine. Try it in both in Rhino 5 (always faster) and Rhino 6 Beta. Here are the times I got recently:

max_v . Rhino 5… Rhino 6 Beta Slowdown
100 … 0.71 sec …2.87 sec …>>> 4X
200 … 1.42 sec …3.69 sec …>>> 2.5X
300 … 2.14 sec …5.72 sec …>>> 3X
400 … 2.86 sec …23.87 sec …>>>8X
500 … 3.56 sec …34.87 sec …>>>10X
600 … 4.75 sec …47.73 sec …>>>10X
700 … 5.05 sec …63.28 sec …>>>12X but Rhino 6 Beta can do this in 24 sec sometimes.
The Rhino 6 Beta times are not very reproducible. For example, I have seen the max_v = 400 case run in only 8 sec but this is still 3X slower than Rhino 5.

The full code for coloring the mesh has 8X as many calls to rs.EvaluateSurface. Apparently this compounds the slowdown, moving it from 12X to over 20X.

import rhinoscriptsyntax as rs
from Rhino.Geometry import Point3d
from time import time
P3d = Point3d
from time import time
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)]
	cx = [[] for i in range(7)]
	cy = [[] for i in range(9)]
	x_last = pts[0][0]
	y_last = pts[0][1]
	j = 0
	k = 0
	for i in range(len(pts)):
		x = pts[i][0]
		y = pts[i][1]
		# Load points for curves in y-direction.
		# This is a row of the pts "table" above.
		if x == x_last:
			# Load points
			cx[j].append(pts[i])
		else:
			# Advance to next curve in y (row in pts "table".
			x_last = pts[i][0]
			j += 1
			cx[j].append(pts[i])
		# Load points for curves in x-direction.
		# This is a column of the pts "table" above.
		cy[divmod(i,9)[1]].append(pts[i])
	crvs=[]
	for i in range(6):
		crvs.append(rs.AddInterpCurve(cx[i]))
	for i in range(9):
		crvs.append(rs.AddInterpCurve(cy[i]))
	# Create surfaces from network of curves.
	srf = rs.AddNetworkSrf(crvs,1,0.01,0.1) # Side-to-top
	rs.DeleteObjects(crvs)
	return(srf)
# Clear document
rs.EnableRedraw(False)
rs.DeleteObjects(rs.AllObjects())
srf = makeBase()
problem(srf)
rs.EnableRedraw(True)

For me this script gives in Rhino 5 the result:

Command: RunPythonScript
Time to fill matrix =  5.62610626221
Command: RunPythonScript
Time to fill matrix =  6.01433563232
Command: RunPythonScript
Time to fill matrix =  5.74410247803
Command: _CommandHistory
Command: RunPythonScript
Time to fill matrix =  5.95180511475
Command: RunPythonScript
Time to fill matrix =  5.86974334717

and in Rhino BETA

Command: RunPythonScript
Time to fill matrix =  4.31267547607
Command: RunPythonScript
Time to fill matrix =  4.32111358643
Command: RunPythonScript
Time to fill matrix =  4.16295623779
Command: RunPythonScript
Time to fill matrix =  4.35050964355
Command: RunPythonScript
Time to fill matrix =  4.25603485107

Running on an i7-7700 @ 3.6GHz, 16GB RAM, Windows 10 (1709)

Nathan has a faster CPU than mine…

V6:

Time to fill matrix =  9.26890563965
Time to fill matrix =  9.30136108398
Time to fill matrix =  9.57987213135
Time to fill matrix =  9.30059814453
Time to fill matrix =  9.31060028076
Time to fill matrix =  9.38266754150

V5:

Time to fill matrix =  7.08924102783
Time to fill matrix =  7.03720092773
Time to fill matrix =  7.02718353271
Time to fill matrix =  7.09025573730
Time to fill matrix =  7.14629364014
Time to fill matrix =  7.02117919922

So, V6 is about 30% slower on this machine. But the times are pretty consistent. Some of the slight inconsistencies in V6 may be due to running it from the script editor, which I find still a bit flaky. Seems to be a bit more consistent if I run the script from a toolbar button.

This is on a 3rd gen i7 3820 running at 2.4Ghz, Win 8.1

–Mitch

@Terry_Chappell - Why are you creating interpolated curves and then a network surface? As your points appear to be in a rectangular form, you might try to work with AddSrfPtGrid which will create an interpolated surface through your grid of points. That won’t change your time much, but it might be “cleaner” - no adding of curves and deleting them again.

–Mitch

@nathanletwory, I get results similar to you an Mitch.

-Pascal

@pascal, except my results are faster for V6

Yes, sorry, I misread the versions - let me do this over…

Rhino version = 5
Max_v = 100.0 Time to fill matrix =  0.513961791992
Max_v = 200.0 Time to fill matrix =  1.04718017578
Max_v = 300.0 Time to fill matrix =  1.54441070557
Max_v = 400.0 Time to fill matrix =  2.13368225098
Max_v = 500.0 Time to fill matrix =  2.6552734375
Max_v = 600.0 Time to fill matrix =  3.05834197998
Max_v = 700.0 Time to fill matrix =  3.60746002197

Rhino version = 6
Max_v = 100.0 Time to fill matrix =  0.756690979004
Max_v = 200.0 Time to fill matrix =  1.30294799805
Max_v = 300.0 Time to fill matrix =  1.87319946289
Max_v = 400.0 Time to fill matrix =  2.53871917725
Max_v = 500.0 Time to fill matrix =  3.08914184570
Max_v = 600.0 Time to fill matrix =  3.76087188721
Max_v = 700.0 Time to fill matrix =  4.40804290771

Debug/release discrepancy, do you think? I’m running in release 6.0

-Pascal

I was able to reduce the time for the case with max_v = 700 to 0.8 sec. To get this amazing time I replaced the call to rs.EvaluateSurface with 2 lines of code, placed outside the v_max loop, to get the surface associated with srf:

srfObj = scriptcontext.doc.Objects.Find(srf)
surface = srfObj.Geometry.Faces[0]

Then inside the loop, rs.EvaluateSurface(srf, i, j) was replaced with surface.PointAt(i,j)

I also used Mitch’s suggestion to use AddSrfPtGrid in makeBase. This speeds it up from 0.15 sec to 0.046 sec (over 3X) and reduces 20+ lines of code to 1.

Now my full mesh coloring code runs in 9.3 sec instead of up to 980 sec. Over 100X faster. Apparently there is something in the overhead to coercesurface or coerceguid (which coercesurface calls) used inside rs.EvaluateSurface that can really bog down the code on some machines. The code I am now using has stripped off all the robustness of the original code. But it is worth the work-around for now. It means no more waiting 16 minutes for 9 sec worth of code. Besides, now my machine, a lowly Yoga 900 with a i7-6500U, 2-core, 2.5 GHz chip that I helped design, leaves yours in the dust (or it did until you try the new code).

The speedup in this case is exceptional because a lot of the overhead in EvaluateSurface may be in setting up surface to evaluate with surface.PointAt. I moved the definition of surface outside the loop so this overhead disappears.

The new code below runs in 0.4 sec in Rhino 5 and my full mesh coloring (with 500,000+ vertices) is done in 7.7 sec. So on my machine, Rhino 5 is up to 2X faster than Rhino 6 Beta.

import scriptcontext
import rhinoscriptsyntax as rs
from Rhino.Geometry import Point3d
from time import time
P3d = Point3d
from time import time
def problem(srf):
	srfObj = scriptcontext.doc.Objects.Find(srf)
	surface = srfObj.Geometry.Faces[0]
	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))
			b.append(surface.PointAt(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)

Regards,
Terry.

1 Like