.NET 2.0 memory usage: 32bit vs 64bit.

F

Frank Rizzo

Hello,

I've compiled my app for AnyCPU setting in vs2005. Then I tried the app
on both 32-bit Windows 2003 R2 and 64-bit Windows 2003 R2. The memory
usage of the application when working on the same data set were
unbelievable (the data is from Task Manager) :

32-bit Windows 2003 R2
Mem Usage: 481,400k
Peak Mem: 583,020k

64-bit Windows 2003 R2
Mem Usage: 934,456k
Peak Mem: 1,254,008k

In other words, basically memory usage doubled. Now I can see that the
size of pointers would double, but most of the data is of type int,
short, string, etc... e.g. types that are the same size on 32 and 64 bit
machines (at least its c# aliases). The object model is pretty big and
it does have a lot of pointers to objects, but the size of the data
dwarfs the potential size of all the pointers, etc...

Why is difference so huge? Or am I measuring the wrong metrics here?

Regards
 
B

Barry Kelly

Frank said:
32-bit Windows 2003 R2
Mem Usage: 481,400k
Peak Mem: 583,020k

64-bit Windows 2003 R2
Mem Usage: 934,456k
Peak Mem: 1,254,008k
Why is difference so huge? Or am I measuring the wrong metrics here?

Have you tried, e.g. SOS or a memory profiler and finding out what
instances are alive and taking up all the space?

-- Barry
 
F

Frank Rizzo

Barry said:
Have you tried, e.g. SOS or a memory profiler and finding out what
instances are alive and taking up all the space?

I have not. Know of any that would work on an x64 box?
 
W

Willy Denoyette [MVP]

Frank Rizzo said:
Hello,

I've compiled my app for AnyCPU setting in vs2005. Then I tried the app on both 32-bit
Windows 2003 R2 and 64-bit Windows 2003 R2. The memory usage of the application when
working on the same data set were unbelievable (the data is from Task Manager) :

32-bit Windows 2003 R2
Mem Usage: 481,400k
Peak Mem: 583,020k

64-bit Windows 2003 R2
Mem Usage: 934,456k
Peak Mem: 1,254,008k

In other words, basically memory usage doubled. Now I can see that the size of pointers
would double, but most of the data is of type int, short, string, etc... e.g. types that
are the same size on 32 and 64 bit machines (at least its c# aliases). The object model is
pretty big and it does have a lot of pointers to objects, but the size of the data dwarfs
the potential size of all the pointers, etc...

Why is difference so huge? Or am I measuring the wrong metrics here?

Regards



What you are looking at is the Process WorkingSet size, not the memory taken by your managed
objects.
Better use the perfmon to watch the GC memory (CLR memory) and the process memory counters.
A memory increase of 30-50% isn't that unusual, but 100% is IMO not even impossible, are you
sure you are comparing the same application using the same Framework version?

Willy.
 
W

Willy Denoyette [MVP]

Willy Denoyette said:
What you are looking at is the Process WorkingSet size, not the memory taken by your
managed objects.
Better use the perfmon to watch the GC memory (CLR memory) and the process memory
counters.
A memory increase of 30-50% isn't that unusual, but 100% is IMO not even impossible, are
you sure you are comparing the same application using the same Framework version?

Willy.



Sorry, this: ... but 100% is IMO not even impossible, ...
should read:
but 100% is IMO even impossible, ...

Note, that there is quite a difference between the JIT32 and the JIT64, and the GC behavior
and generational thresholds are also quite different too. My guess is this application is
using the Server GC on a multi-proc machine, that means that you have several GC heaps . The
space taken by the application code (native run-time libraries, JITted code, CLR stuff
etc..) account for a space increase of ~30%, the size increase of the objects in the GC
heap, depend highly on the number of objects and the number of references (pointer size!)
used as members.

Willy.
 
F

Frank Rizzo

Sorry, this: ... but 100% is IMO not even impossible, ...
should read:
but 100% is IMO even impossible, ...

Note, that there is quite a difference between the JIT32 and the JIT64,
and the GC behavior and generational thresholds are also quite different
too. My guess is this application is using the Server GC on a multi-proc
machine, that means that you have several GC heaps . The space taken by
the application code (native run-time libraries, JITted code, CLR stuff
etc..) account for a space increase of ~30%, the size increase of the
objects in the GC heap, depend highly on the number of objects and the
number of references (pointer size!) used as members.

ok, I'll repeat the test with the perfmon counters you suggested and
will report back.
 
F

Frank Rizzo

What you are looking at is the Process WorkingSet size, not the memory
Willy, specifically, which counters should I be looking at?

Regards
 
W

Willy Denoyette [MVP]

Frank Rizzo said:
Willy, specifically, which counters should I be looking at?

Regards


Start with:
CLR memory counters - #Bytes in all heaps and the Large Object Heap.
and
Process memory counters - Private Byes and Working Set.

Willy.
 
F

Frank Rizzo

Willy said:
ok, I'll repeat the test with the perfmon counters you suggested and
will report back.

Ok, I tested with perfmon as you suggested and the results aren't
better. The counters were all setup per application and all the numbers
are averages (btw, the number barely deviated from the average anyway)

64-bit:
..NET CLR Memory/#Bytes in all Heaps - 1,420,730,503
Process/Private Bytes - 1,524,000,000
..NET CLR LocksAndThreads/# of current logical threads - 9


32-bit:
..NET CLR Memory/#Bytes in all Heaps - 830,413,699
Process/Private Bytes - 781,433,124
..NET CLR LocksAndThreads/# of current logical threads - 9

The last counter is there to make sure that the thread stacks aren't
leaking.

So, as you can see, on the 64-bit end of things, the memory usage is
basically doubled. Is this right? I'll try and use WinDbg next (once I
learn it) and see what else can be uncovered. The numbers basically
didn't change over a long period of time, suggesting that there are no
memory leaks.

Regards
 
F

Frank Rizzo

Here are more complete results:

64-bit:
..NET CLR Memory/#Bytes in all Heaps - 1,420,730,503
..NET CLR Memory/Large Object Heap Size - 160,520,056
Process/Private Bytes - 1,524,000,000
Process/Working Set - 1,498,600,536
..NET CLR LocksAndThreads/# of current logical threads - 9


32-bit:
..NET CLR Memory/#Bytes in all Heaps - 794,603,856
..NET CLR Memory/Large Object Heap Size - 77,166,048
Process/Private Bytes - 842,170,640
Process/Working Set - 840,178,278
..NET CLR LocksAndThreads/# of current logical threads - 9


Numbers do fluctuate (depending on what the application has to do), but
never within more than 50 MB and always return to their original average
values.

Regards
 
W

Willy Denoyette [MVP]

Frank Rizzo said:
Here are more complete results:

64-bit:
.NET CLR Memory/#Bytes in all Heaps - 1,420,730,503
.NET CLR Memory/Large Object Heap Size - 160,520,056
Process/Private Bytes - 1,524,000,000
Process/Working Set - 1,498,600,536
.NET CLR LocksAndThreads/# of current logical threads - 9


32-bit:
.NET CLR Memory/#Bytes in all Heaps - 794,603,856
.NET CLR Memory/Large Object Heap Size - 77,166,048
Process/Private Bytes - 842,170,640
Process/Working Set - 840,178,278
.NET CLR LocksAndThreads/# of current logical threads - 9


Numbers do fluctuate (depending on what the application has to do), but never within more
than 50 MB and always return to their original average values.

Regards



At first sight, I would say you have a lot of small objects [1] in the GC heap, but this
doesn't explain why the 64 bit LOH size is more than 2X the 32 bit LOH. So, there must be
something else going on, probably the difference is with the server mode GC (you are running
this on a multi-proc machine do you?) and the JIT64.
I would take a snap shot of the GC heap by means of Windbg and sos.dll.
[1] the size of smallest object on the CLR is 12 bytes and 24 bytes (32/64 bit
respectively).

Willy.
 
W

Walter Wang [MSFT]

Hi Frank & Willy,

Besides the pointer will have 8 bytes on 64-bit system, you also need to
take data type alignment into account when running on 64-bit:

----8<----

#64-Bit .NET Framework
http://msdn2.microsoft.com/en-us/netframework/aa496329.aspx

On 32-bit platforms, types are aligned on boundaries of the natural lengths
(1, 2, 4, and 8 bytes). On 64-bit platforms, types are aligned on their
natural lengths (1, 2, 4, 8, 10, and 16 bytes), except items that are
greater than 8 bytes in length. Those are aligned on the next power-of-two
boundary. For example, 10-byte data items are aligned on 16-byte boundaries.

---->8----

Also, the LOH contains objects that are 85,000 bytes or bigger:

----8<----

#Maoni's WebLog : Large Object Heap
http://blogs.msdn.com/maoni/archive/2006/04/18/578739.aspx

LOH (Large Object Heap) contains objects that are 85,000 bytes or bigger

---->8----

This means that the count of objects are placed in LOH will be different on
32-bit and 64-bit.

Another factor to consider about is that collections such as ArrayList are
internally using Object[], and the reference to an Object will be 8 bytes
on 64-bit:

----8<----

#Josh Williams : ArrayList' s vs. generic List<T> for primitive types and
64-bits
http://blogs.msdn.com/joshwil/archive/2004/04/13/112598.aspx

...an ArrayList is just a big Object[], and what's an Object[]? It's an
array of references to Objects, references which are bigger on 64-bit
platforms because they have to be able to reference more memory.

---->8----

Hope this helps.

Regards,
Walter Wang ([email protected], remove 'online.')
Microsoft Online Community Support

==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
W

Willy Denoyette [MVP]

Walter Wang said:
Hi Frank & Willy,

Besides the pointer will have 8 bytes on 64-bit system, you also need to
take data type alignment into account when running on 64-bit:

----8<----

#64-Bit .NET Framework
http://msdn2.microsoft.com/en-us/netframework/aa496329.aspx

On 32-bit platforms, types are aligned on boundaries of the natural lengths
(1, 2, 4, and 8 bytes). On 64-bit platforms, types are aligned on their
natural lengths (1, 2, 4, 8, 10, and 16 bytes), except items that are
greater than 8 bytes in length. Those are aligned on the next power-of-two
boundary. For example, 10-byte data items are aligned on 16-byte boundaries.

---->8----

Also, the LOH contains objects that are 85,000 bytes or bigger:

----8<----

#Maoni's WebLog : Large Object Heap
http://blogs.msdn.com/maoni/archive/2006/04/18/578739.aspx

LOH (Large Object Heap) contains objects that are 85,000 bytes or bigger

---->8----

This means that the count of objects are placed in LOH will be different on
32-bit and 64-bit.

Another factor to consider about is that collections such as ArrayList are
internally using Object[], and the reference to an Object will be 8 bytes
on 64-bit:

----8<----

#Josh Williams : ArrayList' s vs. generic List<T> for primitive types and
64-bits
http://blogs.msdn.com/joshwil/archive/2004/04/13/112598.aspx

..an ArrayList is just a big Object[], and what's an Object[]? It's an
array of references to Objects, references which are bigger on 64-bit
platforms because they have to be able to reference more memory.

---->8----

Hope this helps.

Regards,
Walter Wang ([email protected], remove 'online.')
Microsoft Online Community Support

==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.

Walter.
Note, that there are no 10 and 16 byte data types available in the CLR, so this applies to
native code only (C/C++). The only 16-byte aligned data in a "managed" application, is at
the top of a stack frame, and the CLR doesn't have to deal with 16-byte alignment.
Note also that the 10-byte floating point type on X64, are also 16 byte aligned (native code
compilers), that means that the alignment boundaries are 1, 2, 4, 8 and 16.

Willy.
 
F

Frank Rizzo

Walter said:
Hi Frank & Willy,
Another factor to consider about is that collections such as ArrayList are
internally using Object[], and the reference to an Object will be 8 bytes
on 64-bit:

----8<----

#Josh Williams : ArrayList' s vs. generic List<T> for primitive types and
64-bits
http://blogs.msdn.com/joshwil/archive/2004/04/13/112598.aspx

..an ArrayList is just a big Object[], and what's an Object[]? It's an
array of references to Objects, references which are bigger on 64-bit
platforms because they have to be able to reference more memory.


That is probably it. The app was a straight port from .net 1.1 and
pretty much everything is held in ArrayList and HashTable objects. The
problem with ArrayList objects is probably replicated in HashTable
objects as well.

I'll do a bit of refactoring to replace everything with generic
counterparts.
 
J

Jon Skeet [C# MVP]

Frank Rizzo said:
That is probably it. The app was a straight port from .net 1.1 and
pretty much everything is held in ArrayList and HashTable objects. The
problem with ArrayList objects is probably replicated in HashTable
objects as well.

I'll do a bit of refactoring to replace everything with generic
counterparts.

The generic counterparts will only save memory if they're of value
types. If they're of reference types, you'll still get all the "long"
references.
 
W

Willy Denoyette [MVP]

Frank Rizzo said:
Walter said:
Hi Frank & Willy,
Another factor to consider about is that collections such as ArrayList are internally
using Object[], and the reference to an Object will be 8 bytes on 64-bit:

----8<----

#Josh Williams : ArrayList' s vs. generic List<T> for primitive types and 64-bits
http://blogs.msdn.com/joshwil/archive/2004/04/13/112598.aspx

..an ArrayList is just a big Object[], and what's an Object[]? It's an array of
references to Objects, references which are bigger on 64-bit platforms because they have
to be able to reference more memory.


That is probably it. The app was a straight port from .net 1.1 and pretty much everything
is held in ArrayList and HashTable objects. The problem with ArrayList objects is probably
replicated in HashTable objects as well.

I'll do a bit of refactoring to replace everything with generic counterparts.


Adding to what Jon said, try to pre-allocate your ArrayList or the generic counterparts,
else you are running the risk of wasting a lot of memory due to the algorithm used when
expanding the underlying array's.

Willy.
 
W

Walter Wang [MSFT]

Thanks Jon and Willy.

Hi Frank,

Please let us know the result of the suggestions. Thanks.

Regards,
Walter Wang ([email protected], remove 'online.')
Microsoft Online Community Support

==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top