2

I have seen recently a strange behavior in my program. After creating large amounts of objects (500MB of RAM) then releasing them, the program's memory footprint does not return to its original size. It still shows a footprint of 160MB (Private working set).

Normal behavior?

Borland's memory manager does not behave like this, so if possible please confirm (or infirm) this is a normal behavior for FastMM: If you have a handy program in which you create a rather complex MDI child (containing several controls/objects), can you create in a loop 250 instances of that MDI child in memory (at the same time) then release them all and check the memory footprint. Please make sure that you consume at least 200-300MB or RAM with those MDI childs.

Especially those that still using Delphi 7 can see the difference by temporary disabling FastMM.

Thanks


If anybody is interested, especially if you want some proof this is not a memory leak (I hope it is not a mem leak in my code - this is also one of the points of this post: to check if it is my fault), here are the original discussions:

My program never releases the memory back. Why?
How to convince the memory manager to release unused memory

Community
  • 1
  • 1
Gabriel
  • 20,797
  • 27
  • 159
  • 293
  • Hi David. At this point I just want to know why this is happening. – Gabriel Dec 21 '10 at 12:54
  • 2
    How about you write a minimal program that behaves this way and post it here? I don't know what do you expect us to test without doing this. – himself Dec 21 '10 at 13:11
  • Well, if one of the objects I use leaks, then the results will be false even if tested in YOUR computer. Right? I need confirmation from a 3rd party just to be sure this behavior is generated by FastMM. – Gabriel Dec 21 '10 at 13:25
  • 13
    I continue to be perplexed as to why you are investigating this at all. Unless your application is causing paging in memory-constrained systems, you don't need to worry about high working set numbers after releasing a large set of allocations; the working set may be high simply because there is no competing operations to cause the OS to shrink the working set back down. Whether the MM decommits pages, meanwhile, is relatively unimportant unless you are facing address space exhaustion (e.g. a DLL you are using is running out of memory, and it is using a different MM). – Barry Kelly Dec 21 '10 at 13:27
  • I'd say: possible duplicates: %link1%, %link2% – Free Consulting Dec 21 '10 at 13:45
  • @Barry, maybe he should try the garbage collected MM you wrote a few years back. – Kenneth Cochran Dec 21 '10 at 13:51
  • I 100% agree with Kelly. Furthermore FastMM has proven to be an excelent memory manager with high performance. It does not leak memory, trust me. – Runner Dec 21 '10 at 14:28
  • 3
    @Altar You now have Barry Kelly giving you advice. If you don't want to listen to Barry then you really should give up programming! – David Heffernan Dec 21 '10 at 14:32
  • 1
    Personally I suspect that the memory allocation pattern of @Altar's mystery app is producing severe heap fragmentation, but we can only guess. – David Heffernan Dec 21 '10 at 14:33
  • @David - Highly unlikely to be heap fragmentation. Much more likely is that this is simply normal behaviour. – Deltics Dec 21 '10 at 18:15
  • 1
    @Altar - here's a simple test. I am guessing that you are using Task Manager to inspect your app's memory use. After your test run when your app is showing 160MB memory still in use, minimise your app and observe what occurs to the memory figure in Task Manager. I am expecting you to see it plummet. – Deltics Dec 21 '10 at 18:17
  • 1
    @Deltics, yes, however OP ignores the fact what his mem usage assessment method is deficient. I suspect problem is with perceived mem usage rather with real process heap(s). – Free Consulting Dec 21 '10 at 18:45
  • And still we have no idea what kind of memory Altar is measuring. I assume it is the working set size... – Lars Truijens Dec 21 '10 at 19:08
  • 1
    @user205376: OP hasn't yet been *shown* that his methodology is flawed - to say it is being ignored is therefore a little harsh. I was indeed hoping to guide Altar toward recognising this for themselves. – Deltics Dec 21 '10 at 23:50
  • 1
    @Barry: "I continue to be perplexed as to why you are investigating this at all" - - - Well, is always good to know things. If you say that the memory will be released when the system needs it (and see it demonstrated) then I already learned something. Also, I first started by believing I have a leak in my code. Now I know it is not a leak. It is just FastMM "caching" memory. So, another thing I have learned. – Gabriel Dec 22 '10 at 01:03
  • @Barry - To make it simple: I would like to see FastMM releasing the memory. That's all. You say FastMM will release the memory when Windows will need it. If so, I can make another program swallow computer's memory, then check in my program has released the memory. If I see this done I will have peace :) – Gabriel Dec 22 '10 at 01:23
  • Sure you can. Just allocate enough memory in annother app and see the results. – Runner Dec 22 '10 at 06:47
  • 1
    Altar, you're not adequately describing the problem. Here's what you need to do: Post the code of your test program. ("Rather complex MDI child" is no good; it's not a precise description, so it's impossible to duplicate.) Activate the program. Record the memory usage; post a screen shot of your measurement tool showing the current memory usage *before* the test runs. Then push a button to run the test. Show the memory usage *during* the test. Then show the memory usage *after* the test. The "before" and "after" numbers should be similar. Repeat the test with the other MM to show differences. – Rob Kennedy Dec 24 '10 at 18:12
  • 1
    Hi Barry. I just tested your hypothesis and it is TRUE. Thanks a lot. This also confirms that my program is not leaking. Problem solved (well, actually I stopped long time ago to believe it is a problem - I just wanted to know why MY program behaves like this). Thanks again for your most helpful suggestion! – Gabriel Dec 27 '10 at 16:50

5 Answers5

3

Dear Altar, I'm dazzled at how off the point you are in your guesses and how you don't listen to what people told you many times before.

Let's set some things straight. Memory management 101. Please read thoroughly.

When you allocate memory in Delphi, there are two memory managers involved.

System memory manager

First one is a system memory manager. This one is built into Windows and it gives memory in 4kb sized pages.

But it doesn't always give you memory in RAM (or physical memory). Your data can be kept on the hard drive, and read back every time you need to access it. This is awfully slow.

In other words, imagine you have 512Mb of physical memory. You run two programs, each requesting 1Gb of memory. What does OS do?

It grants both requests. Both apps get 1Gb of memory each. Both think all the memory is "in memory". But in fact, only 512Mb can be kept in RAM. The rest is stored in page file, although your app does not know that. It just works slow.

Working set size

Now, what is a "working set size" you are measuring?

It's the part of the allocated memory that is kept in RAM.

If you have an application which allocates 1Gb of memory, and you only have 512 Mb of RAM, then it's working set size will be 512Mb. Although it "uses" 1Gb of memory!

When you run another application which needs memory, OS will automatically free some RAM by moving rarely used blocks of "memory" to the hard drive.

Your virtual memory allocation will stay the same, but more pages will be on the hard drive and less in RAM. Working set size will decrease.

From this, you should have understood by this point, that it's pointless to try and minimize the working set size. You're achieving nothing. You're not freeing memory in any sense. You're just offloading the data to the hard drive.

But the system will do that automatically when it needs to. And there's no point making room in RAM until it's needed. You're just slowing down your application, that's all.

TLDR: "Working set size" is not "how much memory application uses". It's "how much is ready right now". Don't try to minimize it, you're just making things worse.

Delphi memory manager

OS gives you virtual memory in pages of 4Kb. But often you need it in much smaller chunks. For instance, 4 bytes for your integer, or 32 bytes for some structure. The solution?

Application memory manager, such as FastMM or BorlandMM or others.

It's job is to allocate memory in pages from the operating system, then give you small chunks of those pages when you need it.

In other words, when you ask for 14 bytes of memory, this is what happens:

  1. You ask FastMM for 14 bytes of memory.
  2. FastMM asks OS for 1 page of memory (4096 bytes).
  3. OS grants one page of memory, backing it up with RAM (it's stored in actual RAM).
  4. FastMM saves that page, cuts 14 bytes of it and gives to you.

When you ask for another 14 bytes, FastMM just cuts another 14 bytes from the same page.

What happens when you release memory? The same thing backwards:

  1. You release 14 bytes to FastMM. Nothing happens.
  2. You release another 14 bytes. FastMM sees that the 4096 byte page it allocated is now completely unused.
  3. Therefore it releases the page, returning it to the system.

It's worth noting that FastMM cannot release just 14 bytes to the system. It has to release memory in pages. Until the whole page is free, FastMM cannot do a thing. Nobody can.

So, why is my working set size so big, even though I released everything?

First, your working set size is not what you should be measuring. Virtual memory consumption is. But if you have big working set size, your virtual memory consumption will be high too.

What's the problem? You should be able to figure out by this point.

Let's say you allocate 1kb, then 3kb of memory. How much virtual memory have you allocated? 4kb, 1 page.

Now you release 3Kb. How much virtual memory do you use now? 1Kb? No, it's still 1 page. You cannot allocate less than 1 page from the system. You're still using 4096 bytes of virtual memory.

Imagine if you do that 1000 times. 1kb, 3kb, 1kb, 3kb, 1kb, 3kb and so on. You allocate 1000 * 4kb = 4 mb like that, and then you release all the 3kb parts. How much virtual memory do you use now?

Still 4 mb. Because you allocated 1000 pages at first. Of every page you took 1kb and 3kb chunks. Even if you release 3kb chunks, 1kb chunks will continue to keep every single page you allocated in memory. And every page takes 4kb of virtual memory.

Memory manager cannot magically "move" all of your 1kb chunks together. This is impossible, because their virtual addresses can be referenced from somewhere in code. It's not a trait of FastMM.

But why with BorlandMM everything works better?

Coincidence. Maybe it just so happens that BorlandMM gives you memory in a slightly different way than FastMM does. Next thing you know, you change something in your app and BorlandMM acts just like FastMM did. It's impossible for a memory manager to completely prevent this effect, called memory fragmentation.

So what do I do?

Short answer is, not much until this bothers you.

You see, with modern operating systems, you're not really eating anyone's RAM. Per above, OS will automatically swap your pages out when it needs RAM for other applications. This should not be a concern.

And the "excessive" memory isn't lost. Although pages are allocated, 3kb of each is marked as "free". Next time your app needs memory, memory manager will use that space.

But if you really want to help it, you should reorganize your allocations so that the ones you're planning on keeping are done first, and the ones you will soon release are all allocated after that.

Like this: 1kb, 1kb, 1kb, ..., 3kb, 3kb, 3kb...

If you now release all the 3kb chunks, your virtual memory consumption will drop significantly.

This is not always possible. If it's impossible, then just do nothing. It's more or less alright like it is.

And P.S.

You shouldn't be allocating 500 forms in the first place. This is clearly not a way to go. Fix this, and you won't even have a need to think about memory allocation and releasing.

I hope this clears things up, because four posts on the same topic, frankly, is a bit too much.

himself
  • 4,806
  • 2
  • 27
  • 43
  • "You shouldn't be allocating 500 forms in the first place" - Thanks for your advice. A MDI child is created automatically when the user drag and drop a file in my app. I doubt that the user will ever drag and drop more than few files at once. Those 500 forms were created by me only for testing purposes :) Anyway, as you can see anybody else already pointed to the real "problem". But thanks anyway! – Gabriel Dec 27 '10 at 16:44
  • @Altar: Yeah, I have seen people posting countless solutions to all four of those topics you started. But you didn't seem to listen to (or maybe was confused by) what they were saying, which is why I decided to summarize it all. Even in this post, the latest one, you're writing "If you say that the memory will be released when the system needs it then I already learned something", which is wrong... the memory will not be released when the system needs it. – himself Dec 27 '10 at 16:45
  • Or this: "You say FastMM will release the memory when Windows will need it". Wrong, again. FastMM will not release anything automatically. And nobody said that. – himself Dec 27 '10 at 16:46
  • Or this, in your own accepted answer: "My program is not leaking. Problem solved". By forcing your memory to swap into page file, you did *nothing* to check if your app is leaking or not. You clearly don't understand how the memory is managed. Which is why I wrote this post for you, read it. – himself Dec 27 '10 at 16:48
  • Very simple explanation @himself. It's completely right and simply explained. – Rodrigo Farias Rezino Apr 16 '13 at 02:25
2

IIRC, the Delphi memory manager does not immediately return free'd memory to the OS.

Memory is allocated in chunks of small, medium and large sizes, called blocks. These blocks are kept for a while after their contents have been disposed to have them readyly available when another allocation is requested afterwards.

This limits the amount of system calls required for succesive allocation of multiple objects, and helps avoiding heap fragmentation.

sum1stolemyname
  • 4,506
  • 3
  • 26
  • 44
  • Yes, I know!!! And I agree with you. But soooo many people (see previous posts) do not agree with you (and me). – Gabriel Dec 21 '10 at 13:28
  • 1
    @Altar Nobody has disagreed with this. – David Heffernan Dec 21 '10 at 14:22
  • 2
    is correct. I have quite large web server with internal sessions that get created and released. A lot of mamory traffic occurs. I can see that memory does not drop imeddiately, but after few hours it returns to normal. Server can run for more then two months yet memory consumption remains stable. – Runner Dec 21 '10 at 14:26
2

Infirming: Delphi 2007, default memory manager (should be FastMM variation). Several tests on heavy objects:

  1. Initial memory 2Mb, peak memory 30Mb, final memory 4Mb.
  2. Initial memory 2Mb, peak memory 1Gb, final memory 5.5Mb.
himself
  • 4,806
  • 2
  • 27
  • 43
  • Damn. Nothing is consistent. If I look at your data, then I have a leak. But then why if I replace FastMM with Borland's MM I get the same results as you??? Thanks a lot for posting your data. +1 – Gabriel Dec 21 '10 at 13:30
  • 2
    FastMM behaves differently for different memory allocations. Which is why it is important for you to post minimal code that reproduces the behavior on your PC. Else we're just guessing. – himself Dec 21 '10 at 13:40
  • Or you are both measuring different memory statistics. Please Altar, explain what kind of statistics you use to measure. – Lars Truijens Dec 21 '10 at 19:08
  • Altar, how are you measuring that? What are the *other* memory metrics for your program, such as VM size, or private bytes? – Rob Kennedy Dec 24 '10 at 18:21
  • Himself, you've shown the results of a test that you haven't described. How can anyone else reproduce these results? You said you did several tests, but I only see the results of two. Also, *infirm* is not the opposite of *confirm*, but if it were, I don't see how these results would "infirm" that what Altar described is the normal behavior of FastMM. Your results say the memory numbers did not return to their original values, which is what Altar fears to be normal. Doesn't that *confirm*? – Rob Kennedy Dec 24 '10 at 18:23
  • Hi Rob. I am in something else right now but as soon as I finish, I will post the screenshots. The numbers are correct, don't worry :). For the moment I am happy with Kelly's explanation. I will try to confirm it. ["Unless your application is causing paging in memory-constrained systems, you don't need to worry about high working set numbers after releasing a large set of allocations; the working set may be high simply because there is no competing operations to cause the OS to shrink the working set back down. Barry Kelly"] – Gabriel Dec 27 '10 at 00:04
  • @Rob Kennedy: I tried to do just what Altar described he did. Namely, I measured working set, and the results weren't what Altar described (in his case it was 3Mb-500Mb-160Mb). As for "infirm", I'm not a native speaker and the word was new to me too, but I used it because it was mentioned in the original post ("confirm or infirm"). My results show that memory usage returns to it's original values within a margin of acceptable error (compare 30Mb-4Mb and 1024Mb-5Mb, to Altar's 500Mb-160Mb). – himself Dec 27 '10 at 13:10
1

What are the heapmanager stats (GetHeapStatus) on the point that 160MB is still allocated?

Marco van de Voort
  • 25,628
  • 5
  • 56
  • 89
1

SOLVED

To confirm that this behavior is generated by FastMM (as suggested by Barry Kelly) I created a second program that allocated A LOT of RAM. As soon as Windows ran out of RAM, my program memory utilization returned to its original value.

Problem solved. Special thanks to Barry Kelly, the only person that pointed to the real "problem".

Gabriel
  • 20,797
  • 27
  • 159
  • 293
  • You didn't check that your program is not leaking by that. Read my answer. – himself Dec 27 '10 at 16:47
  • 1
    Memory leak is different matter. There is a different topic where we discuss this. I already posted the link to that. Please read my post if you need the link. Anyway, I checked the program against leaks and seems to be ok. – Gabriel Dec 30 '10 at 18:54
  • Glad you finally understand. In my eyes the real problem was that you never explained what kind of memory statistics you where looking at. I actually gave you this answer in one of your previous questions. See http://stackoverflow.com/questions/4493917/when-to-call-setprocessworkingsetsize-convincing-the-memory-manager-to-release/4494448#4494448 – Lars Truijens Jan 01 '11 at 22:59
  • Barry wasn't the only one. I told you about [the folly of using the *working set* to detect a memory leak](http://stackoverflow.com/questions/4475592/4476029#4476029) **three days** before you posted this question. – Rob Kennedy Jan 05 '11 at 07:26