.net sdk memory management problem


#1

Hoi!

I’m diving into the sdk to speed up some routines and have this function that leaks a lot of memory. The function, that is called on each frame, takes an editable mesh and sets the vertex colors based on some criteria. Can anyone spot what I’m doing wrong here?


public void GradeMesh()
{
    IObject obj = node.EvalWorldState(ip.Time, true).Obj;
    IClass_ID cid = global.Class_ID.Create((uint)BuiltInClassIDA.TRIOBJ_CLASS_ID, 0);
    if (obj.CanConvertToType(cid) != 0)
    {
        ITriObject triMesh = obj.ConvertToType(ip.Time, cid) as ITriObject;
        IMesh mesh = triMesh.Mesh;
        IList<IPoint3> mapVerts = mesh.MapVerts(Channel);
        int numMapVerts = mesh.GetNumMapVerts(Channel);

        for (int i = 0; i < numMapVerts; i++)
        {
            IPoint3 vertex = mesh.GetVert(i);
            IPoint3 mapVert = mapVerts[i];

            float r = mapVert.X * 255.0f;
            float g = mapVert.Y * 255.0f;
            float b = mapVert.Z * 255.0f;

            if (vertex.Z > HeightThreshold)
            {
                float newG = Math.Max(0, (g - FadeInValue) / 255);
                float newB = Math.Max(0, (b - FadeInValue) / 255);

                mesh.SetMapVert(Channel, i, global.Point3.Create(1, newG, newB));
            }
            else if (g < 255)
            {
                if (g + FadeOutValue > 255)
                    mesh.SetMapVert(Channel, i, global.Point3.Create(1, 1, 1));
                else
                    mesh.SetMapVert(
                        Channel, i, global.Point3.Create(
                            1, (g + FadeOutValue) / 255, (g + FadeOutValue) / 255));
            }
        }
        triMesh.Dispose();
        mesh.Dispose();
    }
    obj.Dispose();
}


thanks!


#2

i don’t know .net sdk well but in c++ you have to delete TriObject after use.
something like:

TriObject *tri = (TriObject *)ob->ConvertToType(MAXScript_time(), triObjectClassID);
/***** do things **********/
if (ob != tri) tri->AutoDelete(); 

#3

oops… i see you do .Dispose(). it deletes the triObject, but nothing can guaranty that it does do garbage collection.


#4

btw… how faster is c# version vs mxs?


#5

as i see c# function is not undoable in itself. what do you do to make it undoable?


#6

The mxs version did perform well on ~1k vertices, but it had problems with anything much higher than that. The .net function runs at 15 fps on my old machine with 100k vertices.

I have not even thought about making it undoable, but I guess that don’t matter since it is evaluated at each time step


#7

There is a bug in MaxScript when setting the vertex colors of a mesh using the meshop methods. The bug may extend to poly if we talk about performance.

 Using default mesh operations on default color channels performs fast and clean.
 
 The same using meshop is much slower and there is a huge leak of memory. The leak does not happen in the MaxScript Heap. But on the system memory.
 The polyop version does not leak too much, but it is twice as slow as the meshop operation.
 
 Here are some tests:
(
     	gc()
     	delete objects
     	node = converttomesh (geosphere showvertexcolors:on)
     	defaultvcfaces node
     
     	st = timestamp(); sh = heapfree
     	for i = 1 to 100000 do setvertcolor node (random 1 node.numverts) (random black white)
     	format "time:% ram:% 
" (timestamp()-st) (sh-heapfree)
     )

– time:91 ram:128L

Meshop version
Watch the System Memory when running the following as Max might crash without advice.

(
     	gc()
     	delete objects
     	node = converttomesh (geosphere showvertexcolors:on vertexcolortype:5 vertexcolormapchannel:1)
     	channel = 1
     	meshop.defaultmapfaces node channel
     	
     	meshop_setvertcolor = meshop.setvertcolor 
     	st = timestamp(); sh = heapfree
     	for i = 1 to 100000 do meshop_setvertcolor node channel (random 1 node.numverts) (random black white)
     	format "time:% ram:% 
" (timestamp()-st) (sh-heapfree)
     )

– time:1452 ram:128L[color=DarkOrange] / 1.3 Gb of RAM
[/color]
Polyop version:

(
     	gc()
     	delete objects
     	node = converttopoly (geosphere showvertexcolors:on vertexcolortype:5 vertexcolormapchannel:1)
     	channel = 1
     	polyop.defaultmapfaces node channel
     	
     	polyop_setvertcolor = polyop.setvertcolor 
     	st = timestamp(); sh = heapfree
     	for i = 1 to 100000 do polyop_setvertcolor node channel (random 1 node.numverts) (random black white)
     	format "time:% ram:% 
" (timestamp()-st) (sh-heapfree)
     )

– time:2786 ram:128L

Now that you mention this, the bug could perhaps be in the Max core?


#8
meshop.setvertcolor

is very slow method (the same for polyop). there is a post where i explained why it’s slow. so instead of using

setvertcolor

use setmapvert for a specified channel


#9

Thank you Denis. Yes I am aware of the meshop.setmapvert function, although I never needed to use it so I just use setvertcolor, which is much faster.

Even when meshop.setvertcolor is over 20 times slower than setvertcolor, there is a bug that leaks a lot of memory.

There are not too many bugs that leak as much memory as this one in MaxScript that I am aware of.


#10

hehhh… i have to tell it again… setvertcolor is a GEO index based function, but the value is set to a MAP channel vert(s)

so the setvertcolor function with EVERY call looks a geo-map-geo vert corresponding. it’s very-very slow.


#11
And here we go again... I don't know what MaxScript you are talking about, but in my version there is no "setmapvert" function except the one in the meshop struct "meshop.setmapvert". 

Now, this allows you to use a different channels than the default ones. That's the only difference I know it has compared with the pure "setvertcolor" (not meshop.setvertcolor).

So, as I don't need to use another color vertex channel than 1, I just use set vertcolor, because "meshop.setmapvert" is much slower.

Does that make sense or I am missing something?

EDITED: Added code for comparison:

(
   	/* SETVERTCOLOR */
   	delete objects
   	node = converttomesh (geosphere showvertexcolors:on)
   	defaultvcfaces node
   
   	gc(); seed 1
   	st = timestamp(); sh = heapfree
   	for i = 1 to 100000 do setvertcolor node (random 1 node.numverts) (random black white)
   	format "setvertcolor:		%ms ram:% 
" (timestamp()-st) (sh-heapfree)
   	
   	/* MESHOP.SETMAPVERT */
   	node = converttomesh (geosphere showvertexcolors:on vertexcolortype:5 vertexcolormapchannel:1 pos:[60,0,0])
   	channel = 1
   	meshop.defaultmapfaces node channel
   
   	gc(); seed 1
   	st = timestamp(); sh = heapfree
   	meshop_setmapvert = meshop.setmapvert
   	for i = 1 to 100000 do meshop_setmapvert node channel (random 1 node.numverts) ((random black white)/255)
   	format "meshop.setmapvert:	%ms ram:% 
" (timestamp()-st) (sh-heapfree)
   	
   )

– setvertcolor: 78ms ram:120L
– meshop.setmapvert: 172ms ram:192L


#12

I believe the leaking in the .Net function is caused by the IPoint3 creation. I tried the following and the memory keeps calm and low. I didn’t even needed to dispose any object or gc(), but it might be required.

I've tried 1000 calls on a 100K object.
public void GradeMesh()
    {
    	IObject obj = node.EvalWorldState(ip.Time, true).Obj;
    	IClass_ID cid = global.Class_ID.Create((uint)BuiltInClassIDA.TRIOBJ_CLASS_ID, 0);
    	if (obj.CanConvertToType(cid) != 0)
    	{
    		ITriObject triMesh = obj.ConvertToType(ip.Time, cid) as ITriObject;
    		IMesh mesh = triMesh.Mesh;
    		IList<IPoint3> mapVerts = mesh.MapVerts(Channel);
    		int numMapVerts = mesh.GetNumMapVerts(Channel);
    
    		IPoint3 Pt1 = global.Point3.Create(1, 0, 0);
    		IPoint3 Pt2 = global.Point3.Create(1, 1, 1);
    		
    		for (int i = 0; i < numMapVerts; i++)
    		{
    			IPoint3 vertex = mesh.GetVert(i);
    			IPoint3 mapVert = mapVerts[i];
    
    			float r = mapVert.X * 255.0f;
    			float g = mapVert.Y * 255.0f;
    			float b = mapVert.Z * 255.0f;
    
    			if (vertex.Z > HeightThreshold)
    			{
    				Pt1.Y = Math.Max(0, (g - FadeInValue) / 255);
    				Pt1.Z = Math.Max(0, (b - FadeInValue) / 255);
    				mesh.SetMapVert(Channel, i, Pt1);
    			}
    			else if (g < 255)
    			{
    				if (g + FadeOutValue > 255)
    				{
    					mesh.SetMapVert(Channel, i, Pt2);
    				}else{
    					Pt1.Y = (g + FadeOutValue) / 255;
    					Pt1.Z = (g + FadeOutValue) / 255;
    					mesh.SetMapVert(Channel, i, Pt1);
    				}
    			}
    		}
    		// triMesh.Dispose();
    		// mesh.Dispose();
    	}
    	// obj.Dispose();
    }

#13

Thanks! I will test it when I get back from work. I noticed that the IPoint3 also implements IDisposable, so i’ll call that one too.


#14
I noticed that the IPoint3 also implements IDisposable, so i'll call that one too.

Why on earth would they implement Point3 as a class, not to mention one which requires disposal???

It’s 3 freaking floats!


#15

i was missing… :slight_smile:
i was taking about poly methods:

(
	delete objects
	node = converttopoly (geosphere segments:20)

	t1 = timestamp()
	m1 = heapfree

	setmapvert = polyop.setmapvert
	for v=1 to node.numverts do setmapvert node 1 v red
	format "map >	 verts:% time:% memory:%
" node.numverts (timestamp() - t1) (m1 - heapfree)

	gc()
	
	t1 = timestamp()
	m1 = heapfree

	setvertcolor = polyop.setvertcolor
	for v=1 to node.numverts do setvertcolor node 1 v red
	format "col >	 verts:% time:% memory:%
" node.numverts (timestamp() - t1) (m1 - heapfree)
)

#16

Disposing the IPoint3 also seems to prevent the leaking, but the function seems to run twice as slow when creating/disposing the IPoint3 than if I just overwrite the values as shown in the code above.

Overwriting IPoint3 values (1000 calls to GradeMesh())
– time:5135 ram:95864L verts:100002

Creating IPoint3 (1000 calls to GradeMesh())
– time:9889 ram:95864L verts:100002


#17

Shouldn’t you also be disposing the IList<IPoint3> returned from mesh.MapVerts(Channel) and the IPoint3’s returned from mesh.GetVert(i)?


#18

.net sdk needs a method: DisposeAll() :slight_smile:


#19

As far as I can tell, IPoint3 doesn’t really contain the X,Y,Z data, it’s just a wrapper around a pointer to a native Point3 in unmanaged memory. I think what you’re disposing is actually the pointer reference.

Or maybe it’s telling the unmanaged memory manager to free the native Point3? I don’t know. I’d check the documentation if one existed.


#20

I would suppose you would have to dispose everything that is disposable.

In this case, GetVert() does not cause any memory leaking at all, so I tried to not dispose anything and see what was causing the leaking.

It appears that the leaking is directly related to the IPoint3 creation, and so I would assume it also happens with any other class too. For example IPoint2 has the same problem.

Also Creating/Disposing seems to be much slower than overwriting, so I would personally just go with Creating the IPoint3 once and overwrite its values later. This way I think you can also omit disposing it without any worries.