Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

32 bit vs. 64 bit memory

4.64/5 (9 votes)
14 Jan 2013CPOL11 min read 39.6K  
Why is your Application's Memory platform dependent?

Objective

The intention of this article is to assist the readers realize why a same piece of code when executed on 32 bit environment, WOW (Windows on Windows) environment and 64 bit environment, consumes different amounts of memory.<o:p>

Introduction

It’s known and evident that running an application over WOW consumes more memory than 32 bit and running it on 64 bit consumes more than WOW and 32 bit environment.

Although there is no definite formula to find out the exact percentage increase in memory, the below discussion, by comparing 32 bit against 64 bit and 32 bit against WOW, helps understand what causes the memory usage to increase.<o:p>

Background

Reader who has the basic knowledge of WOW, 32 bit applications, 64 bit applications and platform porting background would make the most of this article.

To be on the safer side, if you decide to go through this article in any case, let us summarize WOW in one line.

"WOW simulates an environment of a different platform than the one which is sitting beneath, so that, an application which would have otherwise been incompatible will now run."

32 bit Vs 64 bit

The primary change between 64 bit and 32 bit is the width of the address field, which has increased to 8 bytes from 4 bytes. So, evidently, more the number of address fields \ pointers in our application, more is the memory consumption in 64 bit. This document is as an exercise to traverse through a .Net process and explore all the underlying address fields/ pointers, present at different places with in a process, which ultimately remains responsible for the increase in memory consumption in a 64 bit process over a 32 bit process.

Data Alignment

One of the primary reasons for the increase in memory is Data alignment. Data alignment is putting the data at a memory offset which is a multiple of the "WORD" size.

When a processor reads from or writes to the memory, it is in "WORD" sized chunks which is, 4 bytes in a 32 bit environment and 8 bytes in a 64 bit environment. When the size of a given object doesn't make up to the multiple of "WORD", the operating system will have to make it equal to the size of the very next multiple of "WORD". This is done by adding some meaningless information (Padding) at the end of the object.

In a 32 bit .Net process, the size of an object is at least 12 bytes and in a 64 bit .Net process, it is at least 24 bytes. The header which consists of two pointer types takes away 8 bytes and 16 bytes respectively in 32 and 64 bit environment. Even if the object doesn't have any members with in it, it consumes those additional 4 bytes \ 8 bytes for padding purpose which makes it's size 12 and 24 respectively in 32 bit and 64 bit process.

If an object in 32 bit environment having only one member which is short, the size of that object should be ideally size of header(8 bytes) + size of short (2 bytes). But, it ends up being size of the header (8bytes) + size of short (2 bytes) + padding bytes (2bytes) and hence data alignment leads to unavoidable wastage of memory. This wastage gets exaggerated in 64 bit environment simply because the WORD size becomes 8 bytes instead of 4 bytes. If an object in 64 bit has just one member which is a short it would take 24 bytes (16 bytes of header + 2 bytes of short + 6 bytes of padding \ adjustment). The wastage \padding is not a constant factor in each object instead it depends on the ‘Type’ and ‘Number’ of members of that object.

Eg: The object which contains just one ‘int’ and pays 24 bytes could have had 2 ‘int’s at the same cost resulting in zero wastage. An object which contains just one ‘short’ at the cost of 24 bytes could have 4 ‘short’s at the same cost and zero wastage.

Object Header

Any .NET object would have a sync block (pointer to a sync block) and a type handler (Method table pointer). The header size increases by 8 bytes in 64 bit since the header essential has two pointers and pointer in 64 bit is 8 bytes against 4 byte in 32 bit environment. This means, if an application has 10000 objects (be it of any type) the memory straight away increases by 80,000 bytes between 32 and 64 bit environments, even if they are blank objects. <o:p>

The Stack

The stack segment of the process does contribute to the increase in memory in 64 bit as well. Each item \line in a stack has two pointers one for the callee address and the other being the return address. Just to get a feel of how significant it’s contribution is, let us consider the below program.

namespace Memory_Analysis {
class Program {
     static void Main(string[] args) {
     A obj = new A();
     Console.ReadLine();
     }
}
class A {
     char Data1;
     char Data2;
     short Data3;
     int Data4;
}

The stack segment for this code when executed would be having around 6000 lines(Measured by SOS). 6000 lines would result in 1200 addresses (pointers) fields because, as said above, each line in the stack would have two addresses a callee and a return address. Each address field leads to an increase of 4 bytes in 64 bit. So there will be an increase of (1200 * 4) 4800 bytes in the stack segment itself for the code segment which is as small as above.

Method Table

Now coming to the method tables, each class which has at least one live instance would have a method table. Each method table would again have 2 address fields (entry point and description). If an application has 100 methods including all the classes within it, that would lead to ((100* 2) * 4) 800 bytes of increased memory in 64 bit just because of the method tables. Similarly, others who have address fields and contribute to the memory increase are GCHandles and FinalizationQueue.

Assemblies

Other than the stack and the heap, the assemblies that get loaded in to its AppDomain also contribute to the increase in memory. Below is a snap shot of the header of an AppDomain. As we can see, there are at least 15 address fields in the header.

Parent Domain: 0014f000
ClassLoader: 001ca060
System Domain: 000007fefa1c5ef0
LowFrequencyHeap: 000007fefa1c5f38
HighFrequencyHeap: 000007fefa1c5fc8
StubHeap: 000007fefa1c6058
Stage: OPEN
Name: None
Shared Domain: 000007fefa1c6860
LowFrequencyHeap: 000007fefa1c68a8
HighFrequencyHeap: 000007fefa1c6938
StubHeap: 000007fefa1c69c8
Stage: OPEN
Name: None
Assembly: 00000000011729a0
Domain 1: 00000000003a34a0
LowFrequencyHeap: 00000000003a34e8
HighFrequencyHeap: 00000000003a3578
StubHeap: 00000000003a3608
Stage: OPEN
SecurityDescriptor: 00000000003a4d40
Name: ConsoleApplication2.vshost.exe

After the header, the Appdomain would consist of a list of all the assemblies within the app domain. Under each assembly it again consists of a list of all the modules within that assembly.

Below is a portion of snap shot of the list of assemblies and the modules within each assembly. The below snap shot contains only that portion of the AppDomain which has reference to our sample "Memory_Analysis" assembly.

Assembly: 000000001ab3c330 [C:\Users\ing06996\Documents\Visual Studio 2008\Projects\ Memory_Analysis \ Memory_Analysis \bin\x64\Debug\ Memory_Analysis.exe]
ClassLoader: 000000001ab3c3f0
SecurityDescriptor: 000000001ab3b5b0
Module Name
000007ff001d1b08 C:\Users\ing06996\Documents\Visual Studio 2008\Projects\ Memory_Analysis \ Memory_Analysis \bin\x64\Debug\ Memory_Analysis.exe

The AppDomain which loads our sample application "Memory_Analysis" has to also load all the referenced dlls from our sample applications, including the .Net dlls like MSCOREE.dll and MSCORWKS.DLL. For each such referenced DLL, there would be entries similar to the one shown in the above snapshot.

Further to this, within each module, there would be several address fields as mentioned below in the snapshot

Assembly: 0090ae40
LoaderHeap: 00000000
TypeDefToMethodTableMap: 00170148
TypeRefToMethodTableMap: 00170158
MethodDefToDescMap: 001701a4
FieldDefToDescMap: 001701b4
MemberRefToDescMap: 001701c8
FileReferencesMap: 00170214
AssemblyReferencesMap: 00170218
MetaData start address: 0016207c

Upon measuring using SOS & WinDBG, a simple assembly like our "Memory_Analysis" had around 80 address fields loaded in the AppDomain which means an increase in memory by (80 * 4) 320 bytes . More the number of referenced assemblies and more the number of modules, higher will be the memory consumption.<o:p>

32 bit vs. WOW

After having compared 32 bit Vs 64 bit, let us now explore the differences between running a 32 bit process on a 32 bit environment and running a 32 bit process on a WOW environment.

WOW (Windows on Windows) as we know, is a simulated environment where a 64 bit Operating system provides a 32 bit environment so that the 32 bit processes can run seamlessly. The trigger point to this discussion is the fact that, running a 32 bit process on WOW takes more memory than running a 32 bit process on 32 bit environment. The discussion below, tries to explore some of the reasons why running on a WOW ends up consuming more memory. Before finding out the reasons for hike in memory, it is important to realize the magnitude of hike. Yet again there is no formula to find out the exact percentage increase in memory when run on WOW mode. Nevertheless, an example and some explanation might help us realize the magnitude of increase. Let us consider the below piece of code.

class MemOnWOW
{
     int i;
     private MemOnWOW()
     {
          i = 10;
     }
     static void Main(string[] args)
     {
          MemOnWOW p = new MemOnWOW();
          System.Console.ReadLine();
     }
}

This when built for a 32 bit platform and executed in a 32 bit environment consumes a total size of 80,596 KB and when executed over a WOW environment, consumes a total size of 115,128 KB, which means an increment of 34,532 KB. ** Total size: Total size includes Managed heap, Unmanaged Heap, Stack, Image, Mapped Files, Shareable, Private data and Paged tables. Now, let us list down the contribution to the increase in memory by each of the segments within the total memory.

WOW Images and execution engine

The assemblies of the WOW, which are located at the location C:\windows\sysWOW64, add up to the increase in memory by around 12MB. These assemblies are required to bring up and execute the WOW environment within which the 32 bit process runs. Below is the list of some of the Dlls which are required to bring up the WOW environment and their roles.

  • Wow64.dll

Manages process and thread creation. Exception dispatching File system redirection Registry Redirection

  • Wow64Cpu.dll

Manages the 32-bit CPU context of each running thread inside Wow64. Provides processor architecture-specific support for switching CPU mode from 32-bit to 64-bit and vice versa.

  • Wow64Win.dll

Intercepts the GUI system calls.

It is important to understand the overhead added by WOW execution environment. You may want to go through the detailed explanation given by Mark Russinovich in his book Windows Internals.

Whenever there is a system call which is made to the underlying kernel (Call to the API exposed by the kernel) or whenever there is a call back from the kernel, the input parameters and output parameters have to be converted from 32 bit to 64 and 64 to 32 bit.

Wow which sits in between has the responsibility of converting the 32 bit user mode object to 64 bit user mode object before sending it to the kernel. Similarly, WOW will also have to convert 64 bit kernel objects to 32 bit kernel object before presenting it to the 32 bit user application. Similar conversions do take place when the kernel throws an exception object and it has to reach the user application. This is also called as "Exception hooking \ Exception Dispatching". Evidently, in the scenarios explained above where there are additional intermediate (user and kernel) objects created, memory is bound to increase. These conversions between kernel and user mode can also lead the performance degradations and not just the memory increase.

Mapped Files

During the execution of a .Net application there could be several files which get memory mapped. Some of them are the globalization files (*.nls), font files (*.ttf) etc. When run under WOW, there are additional WOW related files which gets memory mapped and hence leading to memory hike. The number of files which gets memory mapped depends on the kind of assemblies referenced, resources used within the application etc. For the example "MemOnWOW" that we have considered above, the additional mapped files in the WOW mode are the globalization related .nls files which consume around 2.5 MB of excess memory.

Unmanaged Heap

Under the WOW execution, the unmanaged heap would be used by the WOW environment. In our current example, it consumes around 2 MB of the process memory. This unmanaged heap would never be used when a 32 bit managed application is run directly under 32 bit environment.

Managed Heap

Managed heap would be kind enough to stay neutral and consume exactly the same amount of memory, whether it is WOW or direct 32 bit environment.

Private Data

Private data worth around 18 MB is added to the total memory size of the process when run under WOW, because of it being in WOW mode. If there are additional private data because of the application itself, it would be in addition to this 18MB (in WoW).<o:p>

Stack Segment

The stack’s contribution to the increase in memory, when run over WOW, varies from application to application as it depends on the program length (how long the stack is). As we know, Stacks are used to store function parameters, local function variables and function invocation records (who has invoked the function) for individual threads. If there are 3 threads, there would be 3 different stacks in the stack segment.

When run under WOW, for each thread the stack segment would have to maintain 2 different and independent stacks one as the 32 bit stack and the other as a 64 bit stack. So, if there are 3 threads in an application, there would be 6 different stacks in the stack segment when run on WOW.

References

  • VMMAP Documentation
  • Windows Internals by Mark Russinovich
  • blogs.Technet.com

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)