5

How do I set MinWorkingSet and MaxWorking set for a 64-bit .NET process?

p.s. I can set the MinWorkingSet and MaxWorking set for a 32-bit process, as follows:

[DllImport("KERNEL32.DLL", EntryPoint = "SetProcessWorkingSetSize", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
internal static extern bool SetProcessWorkingSetSize(IntPtr pProcess, int dwMinimumWorkingSetSize, int dwMaximumWorkingSetSize);

[DllImport("KERNEL32.DLL", EntryPoint = "GetCurrentProcess", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
internal static extern IntPtr MyGetCurrentProcess();

// In main():
SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, int.MaxValue, int.MaxValue);
Contango
  • 76,540
  • 58
  • 260
  • 305
  • 1
    Any reason you don't use the managed api? http://msdn.microsoft.com/en-us/library/system.diagnostics.process.minworkingset(v=vs.110) – rene Aug 30 '12 at 19:30
  • In general, this is a bad idea as the framework will optimize the working set size. What is your requirement? – Maciej Aug 30 '12 at 19:32
  • @Maciej We have run 3 weeks of deep latency analysis on the program, and we are seeing 250us blips in the program due to soft page faults. We are attempting to increase the working set size to reduce the amount of soft page faults. – Contango Aug 30 '12 at 20:05
  • 1
    sorry: [minworkingset](http://msdn.microsoft.com/en-us/library/system.diagnostics.process.minworkingset) [maxworkingset](http://msdn.microsoft.com/en-us/library/system.diagnostics.process.maxworkingset) – rene Aug 30 '12 at 20:11

2 Answers2

7

Don't pinvoke this, just use the Process.CurrentProcess.MinWorkingSet property directly.

Very high odds that this won't make any difference. Soft paging faults are entirely normal and resolved very quickly if the machine has enough RAM. Takes ~0.7 microseconds on my laptop. You can't avoid them, it is the behavior of a demand_paged virtual memory operating system like Windows. Very cheap, as long as there is a free page readily available.

But if it "blips" you program performance then you need to consider the likelihood that it isn't readily available and triggered a hard page fault in another process. The paging fault does get expensive if the RAM page must be stolen from another process, its content has to be stored in the paging file and has to be reset back to zero first. That can add up quickly, hundreds of microseconds isn't unusual.

The basic law of "there is no free lunch", you need to run less processes or buy more RAM. With the latter option the sane choice, 8 gigabytes sets you back about 75 bucks today. Complete steal.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • This is an absolutely brilliant answer, especially the tip that the first element access is the one that trips the soft page fault. We were observing a 250us soft page fault on the first Dictionary.TryAdd() command. – Contango Aug 31 '12 at 14:52
  • Unfortuantely, setting MinWorkingSet doesn't stop the .NET gen2 garbage collection from trimming it back to the bare bones anyway (see the diagram above in my updated answer). – Contango Aug 31 '12 at 20:28
  • I think I might have to set SE_INC_WORKING_SET_NAME and/or SE_INC_BASE_PRIORITY_NAME to get this working, as GC.Collect() trims the working set down anyway. As the scope of the question has changed, I've posted a new question at http://stackoverflow.com/questions/12221541/is-there-a-way-to-expand-the-current-workingset-of-a-process-to-1gb. I wish to thank you for your tips on exactly what causes soft paging faults. – Contango Aug 31 '12 at 22:57
6

All you have to do is change your declaration like so:

[DllImport("KERNEL32.DLL", EntryPoint = "SetProcessWorkingSetSize", 
    SetLastError = true, CallingConvention = CallingConvention.StdCall)]
internal static extern bool SetProcessWorkingSetSize(IntPtr pProcess, 
    long dwMinimumWorkingSetSize, long dwMaximumWorkingSetSize);

The reason is because of the definition of the SetProcessWorkingSetSize function:

BOOL WINAPI SetProcessWorkingSetSize(
  _In_  HANDLE hProcess,
  _In_  SIZE_T dwMinimumWorkingSetSize,
  _In_  SIZE_T dwMaximumWorkingSetSize
);

Note that it doesn't use a DWORD (as 32-bit integer) but a SIZE_T, which is defined as:

The maximum number of bytes to which a pointer can point. Use for a count that must span the full range of a pointer. This type is declared in BaseTsd.h as follows:

typedef ULONG_PTR SIZE_T;

This means that it's a 64-bit value, hence the ability to change to a long and have the function work on 64-bit systems. Also, from the section of MSDN titled "Common Visual C++ 64-bit Migration Issues":

size_t, time_t, and ptrdiff_t are 64-bit values on 64-bit Windows operating systems.

However, this presents a bit of a dilemma, in that you don't want to have to compile platform-specific assemblies (that would be a PITA). You can get around this by taking advantage of the EntryPoint field on the DllImportAttribute class (which you're already doing) to have two method declarations:

[DllImport("KERNEL32.DLL", EntryPoint = "SetProcessWorkingSetSize", 
    SetLastError = true, CallingConvention = CallingConvention.StdCall)]
internal static extern bool SetProcessWorkingSetSize32(IntPtr pProcess, 
    int dwMinimumWorkingSetSize, int dwMaximumWorkingSetSize);

[DllImport("KERNEL32.DLL", EntryPoint = "SetProcessWorkingSetSize", 
    SetLastError = true, CallingConvention = CallingConvention.StdCall)]
internal static extern bool SetProcessWorkingSetSize64(IntPtr pProcess, 
    long dwMinimumWorkingSetSize, long dwMaximumWorkingSetSize);

Now you have two separate signatures. However, knowing which signature to call is still an issue. You don't want to place conditional checks everywhere. To that end, I'd recommend creating a method that performs the check for you and call that.

You'll want to use the Is64BitProcess property on the Environment class to make this determination. Don't use the Is64BitOperatingSystem property. You want the former because 32-bit processes can be run on 64-bit operating systems, and you want to make sure that your code is resilient to that; just checking to see if the operating system is 64 bit doesn't give you the entire picture.

casperOne
  • 73,706
  • 19
  • 184
  • 253