Jump to content
Xtreme .Net Talk

Recommended Posts

Posted (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 by Mike_R

Posting Guidelines

 

Avatar by Lebb

  • Administrators
Posted

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.

Posting Guidelines FAQ Post Formatting

 

Intellectuals solve problems; geniuses prevent them.

-- Albert Einstein

Posted (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 by Mike_R

Posting Guidelines

 

Avatar by Lebb

  • Administrators
Posted (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 by PlausiblyDamp

Posting Guidelines FAQ Post Formatting

 

Intellectuals solve problems; geniuses prevent them.

-- Albert Einstein

Posted (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 by Mike_R

Posting Guidelines

 

Avatar by Lebb

  • Administrators
Posted

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.

Posting Guidelines FAQ Post Formatting

 

Intellectuals solve problems; geniuses prevent them.

-- Albert Einstein

Posted

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 :)

Posting Guidelines

 

Avatar by Lebb

Posted (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 by Mike_R

Posting Guidelines

 

Avatar by Lebb

Posted
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.
Posted

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.

Posting Guidelines

 

Avatar by Lebb

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...