Monday, August 3, 2009

View memory usage

I came across some questions about how to evaluate the memory usage of a process. It seems to be an easy task since most operating systems have provided a set of powerful tools to investigate memory usage. But what confuses the users is the power and complexity of these tools. And different tools may use different term to refer the same concept which shall aggravate the confusion. In this post, I'd try to make things more clear.

Basic concepts:
From a process's point of view, the physical memory isn't directly visible. What is visible to it is the virtual memory space. In other words, each process is given an illusion that there is a memory space (usually 4GB on 32bit system) for it to use exclusively. The OS's memory manager will be responsible for translating the virtual address to physical address. The active process's virtual space doesn't have to be resident in physical memory as a whole. It's managed in pages with a platform dependent granularity, which is 4kb on 32bit windows. Only those pages are currently being used need to be in physical memory. Other pages can be placed in paging file.
When the process attempts to use a virtual address that isn't in physical memory yet, a page fault is triggered. Page fault handler will swap the missing page from paging file to physical memory. Then the faulting instruction which references the missing page will be executed again to visit the page that is now available. The process even doesn't know this happens, it feels that it can use that virtual address directly.
It's common that some standard components are shared by different processes, e.g., the .net framework code can be shared by different .net application. It's a waste of memory if there are several copies in physical memory. The OS allows virtual pages of different process being mapped to the same physical pages to save memory usage.
Let's look at the image below.

There are two processes and their virtual memory layout, organized in pages. Virtual pages in blue color have corresponding page mapped in physical memory, e.g., VP1, VP5, etc. Pink page(VP7) isn't mapped in memory. Yellow page(Page2) in physical memory is a shared page that has several pages(VP1 and VP5) mapped to it.

Understand data in memory monitor:
So, we know that the memory of a process may exist in physical memory or paging file on file system. The utilities we used to monitoring memory usage can show us these data separately.
Microsoft uses different performance counter to monitor them.
Working set is the part of pages that have been swapped in physical memory, so it's usually the one for users who want to know how many memory does an application uses. But it's not very accurate, because the shared pages are counted for every process uses it. It corresponds to memory usage column in Task Manager.
Private bytes is the size of committed memory excluding shared pages. This value indicates the footprint of a process. It corresponds to VM size in Task Manager. Confusing naming convention, right?
Virtual size is the size of committed memory plus the size of reserved memory. It doesn't hurt performance if there is a huge reserved memory because allocating reversed memory doesn't have a significant cost. The memory manager can simply mark a region in virtual space as being reserved without doing actual allocation. This information isn't available in Task Manager.
It's better to use Process Explorer in sysinternal suit to monitor memory because it supplies more information and the naming convention is consistent with Performance Monitor.

On linux platform, command "ps aux" or "ps" can be used to view memory usage. VSZ (virtual set size) and RSS (resident set size) corresponds to virtual size and working set on windows respectively. But it also has the problem of not being accurate for reporting shared pages several times.

Code Sample:
Here is a sample application (windows specific) demonstrates the impacts of different type of allocations of memory.



1 #include
2 #include
3 using namespace std;
4
5 const int ALLOCATION_SIZE = 1024*1024;
6
7 int main()
8 {
9 int cmd;
10 void* wildptr;
11 char* buf = new char[ALLOCATION_SIZE];
12 for(int i = 0; i < ALLOCATION_SIZE; ++i)
13 buf[i] = static_cast<char>(i % 128);
14 while(true)
15 {
16 cout << "allocation type:\n\
17 1: comitted memory\n\
18 2: comitted memory and use\n\
19 3: reversed memory\n\
20 4: new obj on default heap\n\
21 5: new obj on default heap and use\n";
22 cmd = 0;
23 cin >> cmd;
24 switch(cmd)
25 {
26 case 1:
27 wildptr = VirtualAlloc(NULL, ALLOCATION_SIZE, MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
29 break;
30 case 2:
31 wildptr = VirtualAlloc(NULL, ALLOCATION_SIZE, MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
33 memcpy(wildptr, buf, ALLOCATION_SIZE);
34 break;
35 case 3:
36 wildptr = VirtualAlloc(NULL, ALLOCATION_SIZE, MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
39 break;
40 case 4:
41 wildptr = new char[ALLOCATION_SIZE];
43 break;
44 case 5:
45 wildptr = new char[ALLOCATION_SIZE];
47 memcpy(wildptr, buf, ALLOCATION_SIZE);
48 break;
49 default:
50 exit(0);
51 }
52 }
53 return 0;
54 }


The image below shows the result for each allocation type in performance monitor.
For type 1, both private bytes and virtual bytes increases. Working set doesn't change because the memory isn't used.
For type 2, private bytes, virtual bytes and working set all increase because the memory is allocated and used.
For type 3, only virtual bytes increases because the memory isn't committed. Subsequently, memory manager doesn't allocate memory in paging file. This block of memory can't be used until it's committed. Otherwise, access violation is reported.
For type 4 and 5, the result is the same as type 1 and 2 since the heap manger is built on top of virtual memory allocation.

Here are some excerpts from microsoft's perfmon utility:
Page File Bytes:
Page File Bytes is the current amount of virtual memory, in bytes, that this process has reserved for use in the paging file(s). Paging files are used to store pages of memory used by the process that are not contained in other files. Paging files are shared by all processes, and the lack of space in paging files can prevent other processes from allocating memory. If there is no paging file, this counter reflects the current amount of virtual memory that the process has reserved for use in physical memory.

Private Bytes:
Private Bytes is the current size, in bytes, of memory that this process has allocated that cannot be shared with other processes.

Virtual Bytes:
Virtual Bytes is the current size, in bytes, of the virtual address space the process is using. Use of virtual address space does not necessarily imply corresponding use of either disk or main memory pages. Virtual space is finite, and the process can limit its ability to load libraries.

Working Set:
Working Set is the current size, in bytes, of the Working Set of this process. The Working Set is the set of memory pages touched recently by the threads in the process. If free memory in the computer is above a threshold, pages are left in the Working Set of a process even if they are not in use. When free memory falls below a threshold, pages are trimmed from Working Sets. If they are needed they will then be soft-faulted back into the Working Set before leaving main memory.

Get memory usage in code:
Sometimes, it's useful to get memory information in code. There are system apis for this purpose.
GetProcessMemoryInfo and GlobalMemoryStatusEx funtions on windows and getrusage function on linux. It's trivial to provide sample here since preceding links have done.

No comments: