Notifications
Clear all

[Closed] Mini-challenge #6: Attaching 1,000 splines

I ran into a situation where I needed to attach several thousand splines, and found that a linear attach and a cluster attach worked much slower than I expected. denisT suggested I start a challenge to find a new way of attaching a large number of splines, and here we are.

Here is the script that will setup the test scene:


  (   
  	resetMaxFile #noPrompt
  	clearListener()
  	local myCircle = circle radius:10
  	for j=1 to 1000 do
  	(
  		local randCirc = copy myCircle
  		randCirc.pos = [0,0,j]
  	)
  	select $*
  )
  

As a starting point, here are my two attempts at attaching splines:
Linear (attach each spline to the first):


  --select the splines you want to attach, then run the script
  (
  	(
  	source = ""
  	source += "using System;
"
  	source += "using System.Runtime.InteropServices;
"
  	source += "class WindowsGhosting
"
  	source += "{
"
  	source += " [DllImport(\"user32.dll\")]
"
  	source += " public static extern void DisableProcessWindowsGhosting();
"
  	source += "}
"
  
  	csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
  	compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
  
  	compilerParams.GenerateInMemory = on
  	compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
  
  	assembly = compilerResults.CompiledAssembly
  	windowsGhosting = assembly.CreateInstance "WindowsGhosting"
  	windowsGhosting.DisableProcessWindowsGhosting()
  	)
  
  	local ts = timestamp()
  	local mem = heapfree
  	
  	 function splineAttach objArr =
  	 (
  		local myCopy = copy objArr[1];  convertToSplineShape myCopy;  delete objArr[1]
  		for j=2 to objArr.count by 1 do
  		(
  			convertToSplineShape objArr[j]
  			updateShape objArr[j]
  			addAndWeld myCopy objArr[j] 0.0
  			updateShape myCopy
  			if ((random 1 10) == 5) then (print (objArr.count-j))
  		)
  		return objArr.count
  	 )
  	
  	local mySelection = selection as array
  	 local total = splineAttach mySelection
  	
  	format "Time: %ms
" (timestamp()-ts)
  	format "Memory: %
" (mem-heapfree)
  	format "Splines Attached: %
" (total)
   )
  

Cluster:


    --select the splines you want to attach, then run the script
  (
  	(
  	source = ""
  	source += "using System;
"
  	source += "using System.Runtime.InteropServices;
"
  	source += "class WindowsGhosting
"
  	source += "{
"
  	source += " [DllImport(\"user32.dll\")]
"
  	source += " public static extern void DisableProcessWindowsGhosting();
"
  	source += "}
"
  
  	csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
  	compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
  
  	compilerParams.GenerateInMemory = on
  	compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
  
  	assembly = compilerResults.CompiledAssembly
  	windowsGhosting = assembly.CreateInstance "WindowsGhosting"
  	windowsGhosting.DisableProcessWindowsGhosting()
  	)
  
  	local ts = timestamp()
  	local mem = heapfree
  	
  	 function splineAttach objArr =
  	 (
  		 j = 1
  		local theCount = objArr.count
  		 undo off
  		 (
  			 while objArr.count > 1 do
  			 (
  				 if j+1 > objArr.count then (j = 1) else 
  				 (
  					 convertToSplineShape objArr[j];  updateShape objArr[j]
  					 convertToSplineShape objArr[j+1];  updateShape objArr[j+1]
  					 addAndWeld objArr[j] objArr[j+1] 0.0;  updateShape objArr[j]
  					 deleteItem objArr (j+1)
  					 j += 1
  					if ((random 1 10) == 5) then (print (objArr.count))
  				 )
  			 )
  		 )
  		 return theCount
  	 )
  	
  	 local mySelection = selection as array
  	 local total = splineAttach mySelection
  	
  	format "Time: %ms
" (timestamp()-ts)
  	format "Memory: %
" (mem-heapfree)
  	format "Splines Attached: %
" (total)
   )
  

How many different ways are there to attach splines? Which way is the fastest for a large number of splines?
Good luck to everyone!

14 Replies

running your splineAttach function on my machine with the printing commented out give these results:
Time: 556ms
Memory: 136760L
Splines Attached: 1001

my optimised version:

fn splineAttach spline_arr =
(
s = ConvertToSplineShape spline_arr[1]
for i = 2 to spline_arr.count do
(
	ConvertToSplineShape spline_arr[i]
	addAndWeld s spline_arr[i] 0.0
)
updateshape s

s
)

time:176
memory:120L
Splines Attached: 1001

1 Reply
(@denist)
Joined: 10 months ago

Posts: 0

i’m out of max and can’t check the problem, but the numbers above is what i am expecting.
as i said i don’t believe that order of attaching can dramatically change the performance.
what i suggest to do:
#deselect all before attaching
#disable reference messaging
#check how long takes only converting of 1000 shapes
#does addandweld need splineshape? can we using this method attach any shape class object without being converted to splineshape?
#how long takes 1000 (addnewspline and four addknot operations)?

I’m getting different results:


 (	
 	clearListener()
 	
 	fn buildScene = (
 		--resetMaxFile #noPrompt
 		delete $*
 		local myCircle = circle radius:10
 		for j=1 to 99 do
 		(
 			local randCirc = copy myCircle
 			randCirc.pos = [0,0,j]
 		)
 		select $*
 	)
 
 	fn splineAttach spline_arr = 
 	( 
 		s = ConvertToSplineShape spline_arr[1] 
 		for i = 2 to spline_arr.count do 
 		( 
 			ConvertToSplineShape spline_arr[i] 
 			addAndWeld s spline_arr[i] 0.0 
 		) 
 		updateshape s 
 		s 
 	)
 
 	function splineClusterAttach objArr =
 	 (
 		 j = 1
 		local theCount = objArr.count
 		 undo off
 		 (
 			 while objArr.count > 1 do
 			 (
 				 if j+1 > objArr.count then (j = 1) else 
 				 (
 					 convertToSplineShape objArr[j];  updateShape objArr[j]
 					 convertToSplineShape objArr[j+1];  updateShape objArr[j+1]
 					 addAndWeld objArr[j] objArr[j+1] 0.0;  updateShape objArr[j]
 					 deleteItem objArr (j+1)
 					 j += 1
 				 )
 			 )
 		 )
 		 theCount
 	 )
 	
 	buildScene()
 	local ts = timestamp()
 	local mem = heapfree
 		local mySelection = selection as array
 		splineAttach mySelection
 	format "Time: %ms
" (timestamp()-ts)
 	format "Memory: %
" (mem-heapfree)
 	
 	buildScene()
 	local ts = timestamp()
 	local mem = heapfree
 		local mySelection = selection as array
 		splineClusterAttach mySelection
 	format "Time: %ms
" (timestamp()-ts)
 	format "Memory: %
" (mem-heapfree)
 )
 

 Time: 272ms
 Memory: 383736L
 Time: 140ms
 Memory: 379632L
 

^I changed the scene to build only 100 splines, as it was taking too long to attach 1000.
I wonder why the difference is so great between our measurements? Could it be attributed to the version of max you are running? Perhaps some versions of max attach splines faster? Interesting…

Attaching 1001 splines took 196ms on my machine (i7).
My optimized version looked very similar, without the UpdateShape() call within the loop, just one call to it outside. I don’t think attaching 1000 shapes in 1/5th of a second is that bad. Haven’t tested the non-linear code yet.

The original linear code with the prints and the updateShape() inside the loop (which is totally unnecessary) took over 500 seconds!

 lo1

I’m also getting extremely slow results (150 seconds!) even with the updateShape and print removed from the loop. I guess it is version related. I am on max2009.

i’m not sure that we can remove updateshape from loop in general … adding doesn’t need updating, but welding i think does.
in our test sample we have nothing to weld, that’s why we don’t see the problem of doing it without update.

 lo1

thought why not just attach in the loop, and weld once at the end?

2 Replies
(@denist)
Joined: 10 months ago

Posts: 0

i don’t remember spline attach method… it always was a problem in max

(@denist)
Joined: 10 months ago

Posts: 0

the welding in addandweld method is not welding of all close knots. it’s welding of only endpoins. so if you want to do welding at the end of loop, you have to do it in order of attaching

another idea… i see that our test scene populated with shapes using copy method. theoretically copy method shouldn’t leave any referencing between source and target, but it’s in responsibility of developers of cloned class. so try to create all circles independently (not using copy method)

To properly weld/connect splines, I’ve always done a Fuse operation first, to get them to come together, then Weld, which always seems to work for me .

Edit: But I know your meaning is different in regards to this script.

the problem of slow attaching is selected shapes. i tested it in 2010(64) and 2012(64)… both behave the same:

attaching of selected shapes takes for an eternity.
simple linear attaching of 1,000 unselected shapes takes ~130-140ms… and i don’t see any reason to look for a faster algorithm.
here is my test:


  (
      delete objects
      main = converttosplineshape (circle name:"main")
      splines = for k=1 to 300 collect
      (
          circle name:(k as string) pos:[0,0,k]
      )
      gc()
  
  --    select shapes -- try it if you have an eternity...
      t1 = timestamp()
      m1 = heapfree
      with redraw off for s in splines do 
      (
          converttosplineshape s
          addandweld main s 0
      )
      updateshape main
      format "time:% memory:% nodes:%
" (timestamp() - t1) (m1 - heapfree) splines.count
  )
  /*
  -- only convert to splineshape: 
  time:40 memory:120L nodes:1000
  -- convert and add: 
  time:132 memory:120L nodes:1000
  */
  

disabling of reference messaging help a little, but there is no reason to take it in account.

Page 1 / 2