From f2343fb8b9e13bb9a3542e988ad4ae04a621f378 Mon Sep 17 00:00:00 2001 From: Sung Yoon Whang Date: Tue, 15 Oct 2019 22:08:30 -0700 Subject: [PATCH] Improve the performance of Environment.WorkingSet in Windows (#26522) (#27212) * Use win32 api directly for workingset counter * Fix build warnings * Removing useless code * more cleanup * remove size annotation * remove useless comment * Move all the changes to Environment.WorkingSet and remove it from RuntimeEventSourceHelper * removing useless usings * Use kernel32.dll instead of psapi.dll * Code review feedback * Remove newline change * More code review nits --- .../Kernel32/Interop.GetProcessMemoryInfo.cs | 30 +++++++++++++++++++ .../System.Private.CoreLib.Shared.projitems | 1 + .../shared/System/Environment.Unix.cs | 19 ++++++++++++ .../shared/System/Environment.Windows.cs | 15 ++++++++++ .../shared/System/Environment.cs | 24 --------------- 5 files changed, 65 insertions(+), 24 deletions(-) create mode 100644 src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetProcessMemoryInfo.cs diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetProcessMemoryInfo.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetProcessMemoryInfo.cs new file mode 100644 index 000000000000..98234b8e6e08 --- /dev/null +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.GetProcessMemoryInfo.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + [StructLayout(LayoutKind.Sequential)] + internal struct PROCESS_MEMORY_COUNTERS + { + public uint cb; + public uint PageFaultCount; + public UIntPtr PeakWorkingSetSize; + public UIntPtr WorkingSetSize; + public UIntPtr QuotaPeakPagedPoolUsage; + public UIntPtr QuotaPagedPoolUsage; + public UIntPtr QuotaPeakNonPagedPoolUsage; + public UIntPtr QuotaNonPagedPoolUsage; + public UIntPtr PagefileUsage; + public UIntPtr PeakPagefileUsage; + } + + [DllImport(Libraries.Kernel32, EntryPoint="K32GetProcessMemoryInfo")] + internal static extern bool GetProcessMemoryInfo(IntPtr Process, ref PROCESS_MEMORY_COUNTERS ppsmemCounters, uint cb); + } +} diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems index 049a914f76a1..f9a769e1fd7e 100644 --- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems @@ -1047,6 +1047,7 @@ + diff --git a/src/System.Private.CoreLib/shared/System/Environment.Unix.cs b/src/System.Private.CoreLib/shared/System/Environment.Unix.cs index 9a2af4dfb80e..47899cac108b 100644 --- a/src/System.Private.CoreLib/shared/System/Environment.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/Environment.Unix.cs @@ -444,5 +444,24 @@ private static int CheckedSysConf(Interop.Sys.SysConfName name) } return (int)result; } + + public static long WorkingSet + { + get + { + Type? processType = Type.GetType("System.Diagnostics.Process, System.Diagnostics.Process, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: false); + if (processType?.GetMethod("GetCurrentProcess")?.Invoke(null, BindingFlags.DoNotWrapExceptions, null, null, null) is IDisposable currentProcess) + { + using (currentProcess) + { + object? result = processType!.GetMethod("get_WorkingSet64")?.Invoke(currentProcess, BindingFlags.DoNotWrapExceptions, null, null, null); + if (result is long) return (long)result; + } + } + + // Could not get the current working set. + return 0; + } + } } } diff --git a/src/System.Private.CoreLib/shared/System/Environment.Windows.cs b/src/System.Private.CoreLib/shared/System/Environment.Windows.cs index 957d1894a2a8..e2b0918553db 100644 --- a/src/System.Private.CoreLib/shared/System/Environment.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/Environment.Windows.cs @@ -119,5 +119,20 @@ public static string SystemDirectory return builder.ToString(); } } + + public static unsafe long WorkingSet + { + get + { + Interop.Kernel32.PROCESS_MEMORY_COUNTERS memoryCounters = default; + memoryCounters.cb = (uint)(sizeof(Interop.Kernel32.PROCESS_MEMORY_COUNTERS)); + + if (!Interop.Kernel32.GetProcessMemoryInfo(Interop.Kernel32.GetCurrentProcess(), ref memoryCounters, memoryCounters.cb)) + { + return 0; + } + return (long)memoryCounters.WorkingSetSize; + } + } } } diff --git a/src/System.Private.CoreLib/shared/System/Environment.cs b/src/System.Private.CoreLib/shared/System/Environment.cs index 2b638fde9eb3..edf1eeeabe33 100644 --- a/src/System.Private.CoreLib/shared/System/Environment.cs +++ b/src/System.Private.CoreLib/shared/System/Environment.cs @@ -153,30 +153,6 @@ public static Version Version } } - public static long WorkingSet - { - get - { - // Use reflection to access the implementation in System.Diagnostics.Process.dll. While far from ideal, - // we do this to avoid duplicating the Windows, Linux, macOS, and potentially other platform-specific implementations - // present in Process. If it proves important, we could look at separating that functionality out of Process into - // Common files which could also be included here. - Type? processType = Type.GetType("System.Diagnostics.Process, System.Diagnostics.Process, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: false); - IDisposable? currentProcess = processType?.GetMethod("GetCurrentProcess")?.Invoke(null, BindingFlags.DoNotWrapExceptions, null, null, null) as IDisposable; - if (currentProcess != null) - { - using (currentProcess) - { - object? result = processType!.GetMethod("get_WorkingSet64")?.Invoke(currentProcess, BindingFlags.DoNotWrapExceptions, null, null, null); - if (result is long) return (long)result; - } - } - - // Could not get the current working set. - return 0; - } - } - private static bool ValidateAndConvertRegistryTarget(EnvironmentVariableTarget target) { Debug.Assert(target != EnvironmentVariableTarget.Process);