Notifications
Clear all

[Closed] Struct creation leaks

Also I want to “THANK” autodesk max supporting people. It’s The Dead Zone…
so I’ve been challenged and had a fun making my way blindly across this undocumented area.

I’ve kinda understood now what’s going wrong with max developer’s attempts to fix memory leaking issue in ‘local loop scope’ for example.

there is no universal solution, which they try to provide. it’s just NO!

it should be delegated to a developer – how and what need to be collected.

or… the mxs language coding syntax should be changed.

You’ve lost me, but I’ve linked this thread on the Max Core beta forum, so hopefully it might get someone to chime in from Autodesk.

Again Denis, join the beta! We’ve got the Swordslayer in there now… just missing your expert voice.

First of all, using the term ‘leakage’ in this thread is incorrect because, at least from scripts shown, there is no leakage. What is being measured is memory usage. Very different things. Leakage in terms of maxscript means memory that is not being used, but is not freed when a gc occurs.

Let’s look at the original script, slightly tweaked:

(
struct TestStruct
(
a = 0, b = pi, c = x_axis
)
gc()

h = heapfree

for k=1 to 10000 do
(
s = TestStruct()
)

format “heap:% heapPerInstance:%
” (h – heapfree) ((h – heapfree)/10000.)
gc()
format “heap:% heapPerInstance:%
” (h – heapfree) ((h – heapfree)/10000.)
)

run that, and the output is:

heap:1380000L heapPerInstance:138.0
heap:-456L heapPerInstance:-0.0456

If I change the initial value of c from x_axis (a Point3 value) to 0 and run again, results are:

heap:699780L heapPerInstance:69.978
heap:-524L heapPerInstance:-0.0524

Observations.

  1. There is no leakage. On the 2nd line of output, after the gc occurs, memory usage is back down to where we started.
  2. Integer values are non-mutable, and mxs tries to minimize the number of Integer values that exist by reserving Integer values for the range -100 to 100, and also maintains a cache of recently created Integer values. So, saying ‘a=0;b=0;c=0’ doesn’t create any new Integer values, instead existing values are used.
  3. Some value types can be created on the mxs stack and only if need be are they created in heap. An example of this are the Integer values created for the k for loop. We are creating roughly 9900 Integer values, but since their scope is local and they are never held by another value that is in heap, they are created only on the stack, and they don’t need to be garbage collected (popping the stack just makes the values go away).
  4. Point3 values are mutable. So if a script uses a Point3 value, a new Point3 value is created.
  5. A Point3 value requires 68 bytes, and a Struct value requires 68 bytes.
  6. A Value instance (from which all other classes derive) takes 52 bytes. That includes 8 byte pointers for: Value* next, Value* previous, ValueMetaClass*, a 4 byte flag, and a 12 byte AllocatedHeaderData structure that contains the size of the allocated block and some flags. The AllocatedHeaderData is used during gc to hook freed memory back together to form fewer, larger blocks of free memory.
  7. The Struct class adds a pointer to the struct def and a pointer to an array of pointers for the members.
  8. The Point3Value class adds the Point3 member.
  9. You should never ever call collect() on a value or force its deletion. Garbage collection is a process, mxs performs that process, messing with it will cause you grief.
  10. I should make the collect() method and the dtor protected just so people don’t mess with it.

Larry Minton
Principal Engineer
M&E-Product Development Group

1 Reply
(@polytools3d)
Joined: 11 months ago

Posts: 0

You are right, we shouldn’t have called it a leak if we stick to the strict definition of memory leaking, as we can reclaim the memory by using gc() or gc light:on as we have mentioned earlier.

Even if we don’t manually call gc(), it seems the internal gc() kicks in once the heap is fulfilled and it reclaims all used memory.

So, shall we call this unnecessary memory usage a bug? because the behavior in Max 2017 is completely different, which in part explains the big drop in the loops performance.

Just out of curiosity, doesn’t this behavior of using all the heap until we call gc() or the internal gc() kicks in affect the performance or the integrity of the heap or the undo stack?

I don’t know what Autodesk means by ‘not leaking’ but for me if all allocations made in local scope are not being freed it’s a leaking.

I can’t do gc() every time when I expect ‘not memory leaking’.

But I’m very interested to get explanation of this behavior, and I will appreciate anyone who can give it to me:

(
	gc()
	t = timestamp()
	h = heapfree

	count = 10000
	for k=1 to count do
	(
		p = point3 0 0 0 
	)

	format "count:% = time:% heap:%
" count (timestamp() - t) (h - heapfree)
)
(
	gc()
	t = timestamp()
	h = heapfree

	count = 100000
	for k=1 to count do
	(
		p = point3 0 0 0 
	)

	format "count:% = time:% heap:%
" count (timestamp() - t) (h - heapfree)
)
(
	gc()
	t = timestamp()
	h = heapfree

	count = 10000000
	for k=1 to count do
	(
		p = point3 0 0 0 
	)

	format "count:% = time:% heap:%
" count (timestamp() - t) (h - heapfree)
)
(
	gc()
	t = timestamp()
	h = heapfree

	count = 10000000
	for k=1 to count do
	(
		p = point3 0 0 0 
		ok
	)

	format "-count:% = time:% heap:%
" count (timestamp() - t) (h - heapfree)
)

my numbers are:

count:10000 = time:4 heap:120L
count:100000 = time:45 heap:4409488L
count:10000000 = time:6595 heap:25254808L
count:10000000 = time:4578 heap:120L 

I believe that GC is a delicate process, but I have my own understanding of when and how to do collection. After adding my collection method to my most heavy scripts I’ve never seen this very familiar for all max users and mxs developers Error Message:

count:10000 = time:4 heap:120L
count:100000 = time:45 heap:4409488L
count:10000000 = time:6595 heap:25254808L
count:10000000 = time:4578 heap:120L

just look at these numbers… the first one and last one are examples of not leaking, the second and the third are ‘autodesk’ not leaking.

I hope that the using of OK in loop is safe enough, but this trick doesn’t work for Structures and Arrays… My method just makes it works for them as well.

2 Replies
(@polytools3d)
Joined: 11 months ago

Posts: 0

undefined variables also seems to have the same effect than OK. For example:

(
	count = 10000000
	
	gc(); st=timestamp(); sh=heapfree
	for k=1 to count do
	(
		p = point3 0 0 0 
		undefined
	)
	format "time:% heap:%
" (timestamp()-st) (sh-heapfree)

	gc(); st=timestamp(); sh=heapfree
	for k=1 to count do
	(
		p = point3 0 0 0 
		()
	)
	format "time:% heap:%
" (timestamp()-st) (sh-heapfree)
	
	gc(); st=timestamp(); sh=heapfree
	for k=1 to count do
	(
		p = point3 0 0 0 
		asdasdasd
	)
	format "time:% heap:%
" (timestamp()-st) (sh-heapfree)
)
(@denist)
Joined: 11 months ago

Posts: 0

I know. OK just looks more appropriate in this case


(
	delete objects
	d = dummy()
	struct a(node, track, tm, dir)

	gc()

	count = 5000000

	t = timestamp()
	h = heapfree

	for k=1 to count do
	(
		b = a node:d track:d.track tm:d.track.value dir:d.dir
		--collectvalue &b 
	)

	format "count:% = time:% heap:%
" count (timestamp() - t) (h - heapfree)
)

here is what we have:
count:5000000 = time:11239 heap:26685888L

here is what I have if I uncomment –collectvalue &b (which is my function):
count:5000000 = time:9529 heap:272L

temporary deleted

Page 2 / 3