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.
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.
- There is no leakage. On the 2nd line of output, after the gc occurs, memory usage is back down to where we started.
- 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.
- 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).
- Point3 values are mutable. So if a script uses a Point3 value, a new Point3 value is created.
- A Point3 value requires 68 bytes, and a Struct value requires 68 bytes.
- 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.
- The Struct class adds a pointer to the struct def and a pointer to an array of pointers for the members.
- The Point3Value class adds the Point3 member.
- 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.
- 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
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, doesnt 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.
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)
)
(
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