Notifications
Clear all

[Closed] weird crash/leak with scripted simpleObjs …

my guess is you have very tight dependency in your setup. it causes ‘all-time-every-time’ mesh build.

‘mesh build’ usually leaks. you can read about this fact in the mxs help and find about it on this forum.

i don’t really know what to suggest.

try to reuse snapped meshes. because a copy of trimesh is much cheaper than any new mesh snap.

I had literally not heard of that way of caching functions, Ill look into it more and try to implement it. (I guess you just make local variables of all the functions youre going to use? sounds easy but Ill look it up)

Yes, that’s it. It is somewhere in the MXS help I think.

But one thing I dont understand is if those vector operations are leaky, they should be leaky on every evaluation wouldnt they?

Yes, they should, although I don’t think this is your problem.

as for the parameters Im not sure what else I can do, I do need to reference these objects and object settings in parameters dont I? how else would they be saved with the object?

I would check if you are storing objects and updating them constantly and if any of the plugins parameters are tied to a monitor or controller that is making the BuilMesh() event fire up so frequently.

(funny thing is its caused a new problem where the interaction with the driving spline is not just slow (in fact its not) but its like double transformed, it accelerates away in both translate, rotate and scale…gaah. why cant things just work =D )

Whenever you see this behavior, you are near to a Max crash. Save and restart Max, I know no other way to reverse this issue.

Also, I don’t think you need to do a full gc(), just a light and perhaps delayed one, but I would leave that option as a last resource. I would first try to isolate the problem and solve it without calling gc(). Also, calling gc light:on will slow down the whole plugin, depending on where you use it, but sometimes it is needed.

As I see this, you have two problems, one is the memory leaking and the other one the constant call to BuildMesh(). The crash is perhaps related to the memory leak, moreover if it happens out of the MXS heap. And it just gets worse when there are so many updates. But you should fix both of them. The most important to me would be the memory leak.

Below there are a few tests you can run to see how a few things we have mentioned performs. While I don’t think it is just one thing what is causing the troubles, the combination of some of them can lead to very bad performance.

/* Methods caching test */
  (
  	gc()
  	delete objects
  	
  	obj = converttomesh (geosphere segs:32)
  
  	st = timestamp(); sh = heapfree
  	for j = 1 to 100 do
  	(
  		for j = 1 to obj.numfaces do meshop.getfacecenter obj j
  	)
  	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
  	
  	st = timestamp(); sh = heapfree
  	GetFaceCenter = meshop.getfacecenter	-- Keeps memory low
  	for j = 1 to 100 do
  	(
  		for j = 1 to obj.numfaces do GetFaceCenter obj j
  	)
  	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
  )

 
/* SnapshotAsMesh() test */
  (
  	delete objects
  	obj = converttomesh (geosphere segs:64)
  
  	messagebox "Watch out the System Memory!
It will go up around 3.5GB."
  	
  	gc()
  	st = timestamp(); sh = heapfree
  	for j = 1 to 1000 do
  	(
  		tmesh = snapshotasmesh obj
  	)
  	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
  	
  	messagebox "Watch out the System Memory!"
  	
  	gc()
  	st = timestamp(); sh = heapfree
  	for j = 1 to 1000 do
  	(
  		tmesh = snapshotasmesh obj
  		free tmesh	-- Keeps memory low
  	)
  	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
  )

 
/* Copy node mesh test */
  (
  	delete objects
  	obj = converttomesh (geosphere segs:64)
  
  	messagebox "Watch out the System Memory!
It will go up around 3.5GB."
  	
  	gc()
  	st = timestamp(); sh = heapfree
  	for j = 1 to 1000 do
  	(
  		tmesh = copy obj.mesh
  	)
  	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
  	
  	messagebox "Watch out the System Memory!"
  	
  	gc()
  	st = timestamp(); sh = heapfree
  	for j = 1 to 1000 do
  	(
  		tmesh = copy obj.mesh
  		free tmesh	-- Keeps memory low
  	)
  	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
  )


/* Copy node baseobject test */
  (
  	delete objects
  	obj = converttomesh (geosphere segs:64)
  
  	messagebox "Watch out the System Memory!
It will go up around 3.5GB."
  	
  	gc()
  	st = timestamp(); sh = heapfree
  	for j = 1 to 1000 do
  	(
  		tmesh = copy obj.baseobject
  	)
  	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
  	
  	messagebox "Watch out the System Memory!"
  	
  	gc()
  	st = timestamp(); sh = heapfree
  	for j = 1 to 1000 do
  	(
  		tmesh = copy obj.baseobject
  		free tmesh	-- DOES NOT keep memory low. Full GC() needed. No guarantees though.
  	)
  	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
  )


/* Point3 test */
  (
  	gc()
  	
  	st = timestamp(); sh = heapfree
  	pt = [1,1,1]
  	for j = 1 to 1000000 do
  	(
  		pt *= [2,3,4]
  	)
  	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
  
  	st = timestamp(); sh = heapfree
  	pt = [1,1,1]
  	for j = 1 to 1000000 do
  	(
  		pt.x *= 2
  		pt.y *= 3
  		pt.z *= 4
  	)
  	format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
  )


Bear in mind that the ‘Point3 test’ might make you even more confused (at least with newer versions of max; not sure if it’s a good idea to take max 9 results into account). Cf:

(
	fn doSomething val = return val

	gc(); st = timeStamp(); sh = heapFree
	for i = 1 to 1000000 do ()
	format "do nothing			time:%	heap:%
" (timeStamp() - st) (sh - heapFree)

	pt = [1,1,1]
	mult = [1.00001,1.00002,1.00003] -- didn't want 1.#INF from 127th iteration on...

	gc(); st = timeStamp(); sh = heapFree
	for i = 1 to 1000000 do doSomething (pt * mult)
	format "vectorMathFn			time:%	heap:%
" (timeStamp() - st) (sh - heapFree)

	pt = [1,1,1]
	mult = [1.00001,1.00002,1.00003]

	gc(); st = timeStamp(); sh = heapFree
	for i = 1 to 1000000 do local newVar = pt * mult
	format "vectorMathLocal			time:%	heap:%
" (timeStamp() - st) (sh - heapFree)

	pt = [1,1,1]
	mult = [1.00001,1.00002,1.00003]

	gc(); st = timeStamp(); sh = heapFree
	for i = 1 to 1000000 do pt *= mult
	format "vectorMathAssignment	time:%	heap:%
" (timeStamp() - st) (sh - heapFree)

	pt = [1,1,1]
	mult = [1.00001,1.00002,1.00003]

	gc(); st = timeStamp(); sh = heapFree
	for j = 1 to 1000000 do
	(
		pt.x *= 1.00001
		pt.y *= 1.00002
		pt.z *= 1.00003
	)
	format "perPartsAssignment		time:%	heap:%
" (timeStamp() - st) (sh - heapFree)
)
max 9 x86 (couldn't resist, still have an old non-upgradeable NFR version); heapSize 2^26 and more:

do nothing				time:329	heap:64L
vectorMathFn			time:1078	heap:64L
vectorMathLocal			time:1015	heap:64L
vectorMathAssignment	time:1047	heap:36028204L
perPartsAssignment		time:2485	heap:64L

max 2016; heapSize 2^25:

do nothing				time:282	heap:128L
vectorMathFn			time:1406	heap:7452856L
vectorMathLocal			time:1930	heap:103160L
vectorMathAssignment	time:1780	heap:103032L
perPartsAssignment		time:5204	heap:10715432L

max 2016; heapSize 2^26:

do nothing				time:283	heap:128L
vectorMathFn			time:1266	heap:14731112L
vectorMathLocal			time:1559	heap:25711216L
vectorMathAssignment	time:1423	heap:25712208L
perPartsAssignment		time:4380	heap:15109200L

max 2016; heapSize 2^27:

do nothing				time:282	heap:128L
vectorMathFn			time:1116	heap:61987752L
vectorMathLocal			time:1704	heap:5791352L
vectorMathAssignment	time:1569	heap:5791776L
perPartsAssignment		time:4221	heap:49422848L

max 2016; heapSize 2^28:

do nothing				time:281	heap:128L
vectorMathFn			time:1095	heap:61987736L
vectorMathLocal			time:1080	heap:120088384L
vectorMathAssignment	time:912	heap:120088408L
perPartsAssignment		time:3763	heap:143677744L

max 2016; heapSize 2^29 and more:

do nothing				time:276	heap:128L
vectorMathFn			time:1107	heap:61987712L
vectorMathLocal			time:1075	heap:120088352L
vectorMathAssignment	time:888	heap:120088376L
perPartsAssignment		time:2953	heap:392131672L

The same in reverse order of execution:

max 2016; heapSize 2^25:

perPartsAssignment		time:2687	heap:8252424L
vectorMathAssignment	time:1779	heap:102776L
vectorMathLocal			time:1953	heap:102336L
vectorMathFn			time:2142	heap:102392L
do nothing				time:760	heap:1522232L

max 2016; heapSize 2^26:

perPartsAssignment		time:2460	heap:20225992L
vectorMathAssignment	time:1383	heap:33899448L
vectorMathLocal			time:1536	heap:33899544L
vectorMathFn			time:1720	heap:33899544L
do nothing				time:598	heap:12957272L

max 2016; heapSize 2^27:

perPartsAssignment		time:2123	heap:106304048L
vectorMathAssignment	time:1511	heap:9833496L
vectorMathLocal			time:1646	heap:9833640L
vectorMathFn			time:1886	heap:9833608L
do nothing				time:427	heap:55996192L

max 2016; heapSize 2^28:

perPartsAssignment		time:2122	heap:106303912L
vectorMathAssignment	time:888	heap:119985336L
vectorMathLocal			time:1029	heap:119985296L
vectorMathFn			time:1229	heap:119985288L
do nothing				time:428	heap:55996256L

max 2016; heapSize 2^29:

perPartsAssignment		time:2126	heap:106303992L
vectorMathAssignment	time:883	heap:119985336L
vectorMathLocal			time:1031	heap:119985304L
vectorMathFn			time:1224	heap:119985288L
do nothing				time:426	heap:55996320L

Run one by one (max 2016; heapSize 2^29)


 do nothing				time:283	heap:128L
 vectorMathFn			time:1079	heap:61956904L
 vectorMathLocal			time:870	heap:61957376L
 vectorMathAssignment	time:758	heap:64004936L
 perPartsAssignment		time:2084	heap:106303880L

Run in pairs with the empty loop (max 2016; heapSize 2^29):

vectorMathFn			time:1080	heap:61957344L
do nothing				time:426	heap:55996688L

vectorMathLocal			time:870	heap:61957344L
do nothing				time:426	heap:55996680L

vectorMathAssignment	time:754	heap:64004904L
do nothing				time:281	heap:64L

perPartsAssignment		time:2090	heap:106302936L
do nothing				time:427	heap:55995944L

Sooo… if you’d look at these results first, what would be your conclusion?

this test proves that using of ny-component assignment is slower but cheaper for memory use.

count = 5000000
(
	gc()
	st = timeStamp()
	sh = heapFree
	pt = [1,1,1]
	for j = 1 to count do 
	(
		pt *= [1,2,3]
		if (mod j 100000) == 0 do format "	%
" (sh - heapFree)
	)
	format "use point3	time:%	heap:%
" (timeStamp() - st) (sh - heapFree)

	gc()
	pt = [1,1,1]
	st = timeStamp()
	sh = heapFree
	for j = 1 to count do
	(
		pt.x *= 1.000001
		pt.y *= 2.000001
		pt.z *= 3.000001
		if (mod j 100000) == 0 do format "	%
" (sh - heapFree)
	)
	format "components	time:%	heap:%
" (timeStamp() - st) (sh - heapFree)
)

we have an opposite result in some tests above but they tells only that in some situation gc() doesn’t work in local scope.

if you break it like:

count = 5000000
gc()
(
	st = timeStamp()
	sh = heapFree
	pt = [1,1,1]
	for j = 1 to count do 
	(
		pt *= [1,2,3]
		if (mod j 100000) == 0 do format "	%
" (sh - heapFree)
	)
	format "use point3	time:%	heap:%
" (timeStamp() - st) (sh - heapFree)
)
gc()
(
	pt = [1,1,1]
	st = timeStamp()
	sh = heapFree
	for j = 1 to count do
	(
		pt.x *= 1.000001
		pt.y *= 2.000001
		pt.z *= 3.000001
		if (mod j 100000) == 0 do format "	%
" (sh - heapFree)
	)
	format "components	time:%	heap:%
" (timeStamp() - st) (sh - heapFree)
)

the gc() has to work as expected

by using by-component assignment we try to avoid creation a new point3 every time when we assign full point3 value which causes the leaking

Yet the culprit seems to be the assignment of a new value to the existing Point3 value in the multiplication assignment within the for-loop. You will get the exact same result if you replace pt *= [1,2,3] with pt = [1,2,3]. On the other hand if you explicitly create a new local in the loop scope like local newLocal = pt * [1,2,3], it will be all okay. All in all, if you don’t need to change that one value multiple times, there’s no reason to do vector math by component.

Btw. just for fun, first without running it – which of these functions would you expect to leak:

fn fun1 = (local x = x_axis * 10)
fn fun2 = (local x = x_axis; x *= 10)
fn fun3 = (local x = x_axis; mult &x) -- where  fn mult &x = x *= 10
fn fun4 = (local x = x_axis; x = mult x) -- where  fn mult x = x * 10
fn fun5 = (local x = x_axis; do x *= 10 while 0 > 1)
fn fun6 = (local x = x_axis; for i = 1 to 1 do x * 10)
fn fun7 = (local x = x_axis; for i = 1 to 1 do x *= 10)

Hint: what the for loop does and while loop does not?

jorge, thanks for all the advice. youre an absolute trooper.

Aha, I see now what you meant about the parameters. But I dont think that was ever the problem (could be wrong) – Ive occasionally caused those kind of update loops and theyre very obvious when they occur since they immediately start looping and at a constant pace.

Having updated all my objects to replace the snapshots, that crashing issue is gone! Im not sure how stable it is (apparently I lied about the occasional bounding box flickering (and set key button) being gone, that still happens sometimes – often in conjunction with object creation etc)
But. progress has been made. thanks ever so much!

Page 2 / 2