Mike_R Posted December 14, 2004 Posted December 14, 2004 (edited) Ok, this I found interesting... Basically, I took an Array of 1,000,000 Integers and checked the memory usage. The interesting thing is, I would have thought that all the memory is allocated at once, even if all the values are initialized to zero values. However, in assigning values to only half the Array, and then later the other half, I could test and see that Memory Allocation for this Array, was actually incremental: Private Sub Array_MemoryTester() Dim myArray() As Integer Dim baseMemory As Long Dim usedMemory As Long baseMemory = Environment.WorkingSet ' Allocate 4 MB of Memory: ReDim myArray(1000000) 'But only give a value to HALF of them: For i As Integer = 0 To myArray.Length\\2 - 1 myArray(i) = i Next ' Report Memory Usage: usedMemory = Environment.WorkingSet - baseMemory MessageBox.Show(usedMemory.ToString("N0")) ' <-- Reports: 2,140,000 ' Fill the other half: For i As Integer = myArray.Length\\2 To myArray.Length - 1 myArray(i) = i Next usedMemory = Environment.WorkingSet - baseMemory MessageBox.Show(usedMemory.ToString("N0")) ' <-- Reports: 4,300,000 End Sub Assigning values to the first 500,000 elements used up 2 MB. And then filling the other 500,000 elements got it to 4 MB. Given that Integers are 4 bytes each, this is mathematically correct. However, Arrays are not a Queue or a Linked List... I would have thought that an Array's memory allocation would have to be "all or nothing", no? I could see an "incremental" memory allocation occurring with an Array of Object that was Boxing Integers, but this example is Strong Typed. I just do not understand how the Memory allocation is going on here. It also can't be Boxing (or any other reference-type allocation) because the bytes used per element is almost exactly 4, there is no extra overhead involved here. Can anyone explain what is going on? Edited December 14, 2004 by Mike_R Quote Posting Guidelines Avatar by Lebb
HJB417 Posted December 14, 2004 Posted December 14, 2004 you did this in both release and debug mode? Quote
Administrators PlausiblyDamp Posted December 14, 2004 Administrators Posted December 14, 2004 What kind of allocation are you getting before you declare the array and secondly after you declare it but before you start to fill it? Also Environment.WorkingSet is just a record of the memory mapped to your process - this does not give you any idea how this memory is being used or what effect GC is having on this utilisation. If you are trying to track memory / GC in any kind of useful way then you want to investigate tools like the process monitior from http://www.sysinternals.com or CLR Profiler. Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
Mike_R Posted December 14, 2004 Author Posted December 14, 2004 (edited) you did this in both release and debug mode? Yes, the behavior is identical in both modes. What kind of allocation are you getting before you declare the array and secondly after you declare it but before you start to fill it? Sorry I should have shown that. Basically almost no "allocation" is attributed from creating the Array itself. About 300K is all' date=' the rest of the array seems to Allocate Memory linearly, depending on how many values I "fill". Here is a more complete coding:Public Class Form1 Inherits System.Windows.Forms.Form Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Call Array_MemoryTester() End Sub Private Sub Array_MemoryTester() Dim myArray() As Integer Dim oMemUsed As New MyMemoryUsage oMemUsed.Init() oMemUsed.Report() ' <-- Reports 0 ReDim myArray(1000000) oMemUsed.Report() ' <-- Reports 303,104 ' Fill in values for HALF the Array: For i As Integer = 0 To myArray.Length\\2 - 1 myArray(i) = i Next oMemUsed.Report() ' <-- Reports 2,314,240 ' Fill the other half: For i As Integer = myArray.Length\\2 To myArray.Length - 1 myArray(i) = i Next oMemUsed.Report() ' <-- Reports 4,321,280 myArray = Nothing oMemUsed.Report() ' <-- Reports 4,325,376 GC.Collect() oMemUsed.Report() ' <-- Reports 475,136 End Sub End Class Class MyMemoryUsage Private _baseMemory As Long Private _usedMemory As Long Sub Init() _baseMemory = Environment.WorkingSet End Sub Sub Report() _usedMemory = Environment.WorkingSet - _baseMemory MessageBox.Show(_usedMemory.ToString("N0")) End Sub End Class Also Environment.WorkingSet is just a record of the memory mapped to your process - this does not give you any idea how this memory is being used or what effect GC is having on this utilisation. If you are trying to track memory / GC in any kind of useful way then you want to investigate tools like the process monitior from http://www.sysinternals.com or CLR Profiler. Thank you for the info on SysInternals and CLR Profiler! I downloaded the CLR Profiler, but it will take me a while to learn about it... On the other hand, I have played with Environment.WorkingSet a bit and it seems to match what the Task Manager is telling me. Exactly. Also, the behavior I'm getting above does suggest that it is reflective of the GC's actions. (Note that, in the code above, the memory does not seem to release from 'myArray = Nothing' until after 'GC.Collect()' is called.) Interesting, yeah? Or is this all "worthless" because calling Environment.WorkingSet is just useless? It's hard to believe though... it is telling me *something* no? Edited December 14, 2004 by Mike_R Quote Posting Guidelines Avatar by Lebb
Administrators PlausiblyDamp Posted December 14, 2004 Administrators Posted December 14, 2004 (edited) The task manager will match, both show you the physical memory allocated to your process. However .Net will not always release unused memory unless the OS requires it; so you can see large amounts of ram allocated without it meaning anything in regards to the current usage. For example where you have oMemUsed.Report() ' <-- Reports 0 try putting 2 calls to oMemUsed.Report() and see what the result is. I get values similar to (but not consistent with) oMemUsed.Report() ' <-- Reports 0 oMemUsed.Report() ' <-- Reports 700,416 try with 3 calls or 4 calls etc - as you can see the results really are not indicative of any actual change as a direct result of any allocations.... Edited March 1, 2007 by PlausiblyDamp Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
Mike_R Posted December 15, 2004 Author Posted December 15, 2004 (edited) I see what you mean, but I think you are overstating the case. Clearly there is some variability involved, presumably due to some overhead issues. But overall, I do feel thet the figures are indicative, overwhelming the "noise" involved. I tried re-running it with multiple calls to oMemUsed.Report(), to get a feel for the variability. This variablitiy is actually only slight, when compared to the entire phenomenon: Private Sub Array_MemoryTester() Dim myArray() As Integer Dim oMemUsed As New MyMemoryUsage oMemUsed.Init() oMemUsed.Report() ' <-- Reports 0 oMemUsed.Report() ' <-- Reports 331,776 oMemUsed.Report() ' <-- Reports 344,064 oMemUsed.Report() ' <-- Reports 352,252 oMemUsed.Report() ' <-- Reports 352,252 oMemUsed.Report() ' <-- Reports 352,252 oMemUsed.Report() ' <-- Reports 352,252 ReDim myArray(1000000) oMemUsed.Report() ' <-- Reports 368,640 oMemUsed.Report() ' <-- Reports 368,640 oMemUsed.Report() ' <-- Reports 368,640 oMemUsed.Report() ' <-- Reports 368,640 ' Fill in values for HALF the Array : For i As Integer = 0 To myArray.Length \ 2 - 1 myArray(i) = i Next oMemUsed.Report() ' <-- Reports 2,371,584 oMemUsed.Report() ' <-- Reports 2,379,776 oMemUsed.Report() ' <-- Reports 2,379,776 oMemUsed.Report() ' <-- Reports 2,379,776 ' Fill the other half : For i As Integer = myArray.Length \ 2 To myArray.Length - 1 myArray(i) = i Next oMemUsed.Report() ' <-- Reports 4,378,624 oMemUsed.Report() ' <-- Reports 4,378,624 oMemUsed.Report() ' <-- Reports 4,378,624 oMemUsed.Report() ' <-- Reports 4,378,624 myArray = Nothing oMemUsed.Report() ' <-- Reports 4,378,624 oMemUsed.Report() ' <-- Reports 4,378,624 oMemUsed.Report() ' <-- Reports 4,378,624 oMemUsed.Report() ' <-- Reports 4,378,624 GC.Collect() oMemUsed.Report() ' <-- Reports 532,480 oMemUsed.Report() ' <-- Reports 544,768 oMemUsed.Report() ' <-- Reports 444,768 oMemUsed.Report() ' <-- Reports 444,768 End Sub And, again, the Task Manager reflects these movements. I follow what you say about the OS <--> App memory allocations, but there still seems to be something odd here about how this memory is being allocated. That is, unless the Environment.WorkingSet and Task Manager values really are worthless above-and-beyond this variability/overhead issue. But, as a simple place to start, the Task Manager's reported memory consumpition would seem to be a reasonable guide, no? Edited December 15, 2004 by Mike_R Quote Posting Guidelines Avatar by Lebb
Administrators PlausiblyDamp Posted December 15, 2004 Administrators Posted December 15, 2004 there still seems to be something odd here about how this memory is being allocated. that is the fundamental design of .Net. Try running the exact same compiled application on another PC with a different work load, more or less physical memory, more or less available memory and see if the results come out the same. Running on the PC in from of me I only have 256M and I am not getting a drop in the memory reported to be in use even after the GC.Collect call.... Tracing through the app with the CLR profiler though does show the array being allocated on the heap, a consistent memory usage for the duration of the button click, pretty well independant of what taskmanager is reporting. Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
Mike_R Posted December 15, 2004 Author Posted December 15, 2004 Ok, that's interesting. I'll have to test this out a lot more thoroughly. Clearly, it makes no sense that an Array actually be allocated in segments. I thank you for your thoughts on this. I'll let you know if I figure out anything more... Thanks again :) Quote Posting Guidelines Avatar by Lebb
Mike_R Posted December 16, 2004 Author Posted December 16, 2004 (edited) Ok, quick followup: I changed the definition of "memory used" to utilize GC.GetTotalMemory(True): Class MyMemoryUsage Private _baseMemory As Long Private _usedMemory As Long Sub Init() _baseMemory = GC.GetTotalMemory(True) End Sub Sub Report() _usedMemory = GC.GetTotalMemory(True) - _baseMemory MessageBox.Show(_usedMemory.ToString("N0")) End Sub End Class And now it runs exactly as one would expect. Public Class ArrayTesterClass Shared Sub Array_MemoryTester() Dim myArray() As Integer Dim oMemUsed As New MyMemoryUsage oMemUsed.Init() oMemUsed.Report() ' <-- Reports 24 ReDim myArray(1000000) oMemUsed.Report() ' <-- Reports 4,001,964 ' Fill in values for HALF the Array : For i As Integer = 0 To myArray.Length \ 2 - 1 myArray(i) = i Next oMemUsed.Report() ' <-- Reports 4,001,964 ' Fill the other half : For i As Integer = myArray.Length \ 2 To myArray.Length - 1 myArray(i) = i Next oMemUsed.Report() ' <-- Reports 4,001,964 myArray = Nothing oMemUsed.Report() ' <-- Reports 1,948 GC.Collect() oMemUsed.Report() ' <-- Reports 1,948 End Sub End Class Sorry for any confusion, and thank you Plausibly, clearly the Environment.WorkingSet() is not a great command here (which you were repeatedly warning me about). I do wonder what it is reporting however... Anyway, problem solved. Are there any other quick commands that you can think of to help measure Memory Utilized by a Process? (Short of having to use the CLR Profiler?) Edited December 16, 2004 by Mike_R Quote Posting Guidelines Avatar by Lebb
neodammer Posted December 18, 2004 Posted December 18, 2004 I can see where not assigning all the arrays at once would be handy. Think of games for example that use up incredible amounts of memory. Think of games/applications that use 1000's of variables. perhaps its smart in a way and dumb in another if that is indeed the case. Im not really sure if that would slow a program down..or just cause it to use less resources. Kind of a catch-22 in a way. Point is if you use 10000 and it only allocates what you are using out of that 10k then its good for memory but i would think that wouldnt speed up the program any infact may be a hinderance to some developers. Quote Enzin Research and Development
Mike_R Posted December 20, 2004 Author Posted December 20, 2004 Well, it does look like in the end that the Task Manager and Environment.WorkingSet() were giving invalid results, as PlausiblyDamp was trying to warn me... :( Retesting with GC.GetTotalMemory() shows the correct result: the Array allocates in one-shot, as should be expected. The only way that an Array would "allocate as used" would be if the Array held a Reference Types, that is, Objects. In this way, the Array would commence as an Array of Null Pointers and the Objects would allocate as the instances were created. So if a Game or the like were using Objects, then yes, it could "allocate" linearly. However, if one wishes to "allocate as you go" like this, then it's probably better to explicitly use an ArrayList or Dictionary, as these object structures are really ment for this. Quote Posting Guidelines Avatar by Lebb
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.