7

I want to create a tool to simulate memory restrictions to memory stress test other applications. I came up with the following code after going through some google searches, but when running this, the task manager or the resource monitor does not show any difference in memory usage. Just a flat line.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Win32Tute
{
    unsafe class Program
    {
        // Heap API flags
        const int HEAP_ZERO_MEMORY = 0x00000008;
        // Heap API functions
        [DllImport("kernel32")]
        static extern int GetProcessHeap();
        [DllImport("kernel32")]
        static extern void* HeapAlloc(int hHeap, int flags, int size);
        [DllImport("kernel32")]
        static extern bool HeapFree(int hHeap, int flags, void* block);

        private static int ph = GetProcessHeap();

        public static void* Alloc(int size)
        {
            void* result = HeapAlloc(ph, HEAP_ZERO_MEMORY, size);
            if(result == null) throw new OutOfMemoryException("Couldn't execute HeapAlloc");
            return result;
        }

        public static void Free(void* block)
        {
            if(!HeapFree(ph, 0, block)) throw new InvalidOperationException("Couldn't free memory");
        }

        public static void Main(string[] args)
        {
            int blockSize = 1024*1024; //1mb
            byte*[] handles = new byte*[1024];
            Console.WriteLine("Memory before : " + (Process.GetCurrentProcess().PrivateMemorySize64/1024)/1024); // get value in Megabytes
            try
            {
                for(int i=0; i<1024; i++)
                {
                   handles[i] = (byte*)Alloc(blockSize);

                }
            }
            finally
            {
                Console.WriteLine("Memory after  : " + (Process.GetCurrentProcess().PrivateMemorySize64 / 1024)/1024);
                Console.WriteLine("Finished allocating 1024MB memory....Press Enter to free up.");
                Console.ReadLine();
            }

            try
            {
                for(int i=0; i<1024; i++)
                {
                    Free(handles[i]);
                }
            }
            finally
            {
                Console.WriteLine("Memory at the end : " + (Process.GetCurrentProcess().PrivateMemorySize64 / 1024)/1024);
                Console.WriteLine("All allocated memory freed. Press Enter to quit..");
                Console.ReadLine();
            }
        }
    }
}
chamilad
  • 1,619
  • 3
  • 23
  • 40
  • 2
    I tried your code and the HeapAlloc is allocating the memory on the Page File and none on the Physical memory. In Resource Monitor, Commit KB goes to ~1GB and if you see the Process.PagedMemorySize64 property, that shows ~1GB also. So the question is how to force HeapAlloc to allocated memory on physical memory, not the page file. – Simon Dec 02 '11 at 05:13
  • I tried a difference code with Marshal class and AllocHGlobal. At first it also gave the same result. No change in the graphs. Then I called Marshal.Copy() just after AllocHGlobal. Then only the mem allocations were reflected in the monitors. So I'm guessing just allocating would not really reserve space from physical memory. – chamilad Dec 02 '11 at 05:19
  • I'm new to memory management. So I have to blindly try everything. :) – chamilad Dec 02 '11 at 05:20
  • 1
    Why not just `var foo = new byte[100000000];` (x10)? – Igby Largeman Dec 02 '11 at 05:33
  • 3
    You might try writing some data to the memory you are allocating. If you don't use the address space, it probably never has to swap it into physical memory. You may also want to look at the VirtualLock function. You can use it to lock virtual address spaces of your process into physical memory. – user957902 Dec 02 '11 at 23:04
  • Thanks! From what I've done so far also, copying some data seems to be the effective operation here. I'll have a look at VirtualLock too. – chamilad Dec 05 '11 at 06:06
  • Bear in mind that when you are using the WinApi's directly, you are not allocating Managed Memory (GC memory). The GC doesn't "know" about it. You inform it by using the AddMemoryPressure method (and MS recommends that you use this method if your code allocates significant unmanaged memory). http://msdn.microsoft.com/en-us/library/system.gc.addmemorypressure.aspx – JMarsch Dec 13 '11 at 17:47

2 Answers2

7

This kind of thing is almost always a bad idea. If you succeed in creating a program that chews up memory, you'll likely find that doing so doesn't stop the other program from responding. The virtual memory manager will go to heroic efforts to keep the other program running. It will, for example, page your memory hog's data to disk so as to keep the working program's data in memory where it belongs. And if you modify your memory hog so that it locks pages in memory (i.e. doesn't let the pages get swapped out), then the computer will start thrashing.

You're much better off writing diagnostic code in the program you're testing that will let you call SetProcessWorkingSetSizeEx to test how the program responds under varying memory conditions. If you can't modify the program you're testing, you can write a program that gets the test program's handle and calls SetProcessWorkingSetSizeEx, passing that handle.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
3

Ok, so three issues in your post...

  1. There is no need to PInvoke allocation, you can simply use Marshal.AllocHGlobal and FreeHGlobal for this.

  2. If you don't actually use the memory then the system will reserve it but not necessarily place it into the working set. So if you want to HOG memory you will need to first allocate the memory and then continually read/write to that memory. Even this will allow the system to unload pages that are not being allocated, but it will place pressure on the memory.

  3. Generally I have to agree with Jim Mischel, this is not going to help you much.

For what it's worth, here is the program updated to write to all the memory allocated and then start a thread that continually reads the memory. The down side to this is that one thread/cpu will be consumed by this.

static void Main(string[] args)
{
    int blockSize = 1024*1024; //1mb
    byte*[] handles = new byte*[1024];
    Console.WriteLine("Memory before : " + (Process.GetCurrentProcess().PrivateMemorySize64/1024)/1024); // get value in Megabytes
    try
    {
        for(int i=0; i<1024; i++)
        {
            handles[i] = (byte*)Marshal.AllocHGlobal(blockSize);
            //write to the memory
            for (int off = 0; off < blockSize; off++)
                *(handles[i] + off) = 1;
        }
    }
    finally
    {
        //create a thread to ensure the memory continues to be accessed
        ManualResetEvent mreStop = new ManualResetEvent(false);
        Thread memoryThrash = new Thread(
            () =>
                {
                    int ihandle = 0;
                    while (!mreStop.WaitOne(0, false))
                    {
                        for (int off = 0; off < blockSize; off++)
                            if (*(handles[ihandle++ % handles.Length] + off) != 1)
                                throw new InvalidOperationException();
                    }
                }
            );
        memoryThrash.IsBackground = true;
        memoryThrash.Start();

        Console.WriteLine("Memory after  : " + (Process.GetCurrentProcess().PrivateMemorySize64 / 1024)/1024);
        Console.WriteLine("Finished allocating 1024MB memory....Press Enter to free up.");
        Console.ReadLine();

        mreStop.Set();
        memoryThrash.Join();
    }

    try
    {
        for(int i=0; i<1024; i++)
        {
            Marshal.FreeHGlobal(new IntPtr(handles[i]));
        }
    }
    finally
    {
        Console.WriteLine("Memory at the end : " + (Process.GetCurrentProcess().PrivateMemorySize64 / 1024)/1024);
        Console.WriteLine("All allocated memory freed. Press Enter to quit..");
        Console.ReadLine();
    }
}
csharptest.net
  • 62,602
  • 11
  • 71
  • 89