.NET 的 Process 类中提供了查找进程的若干方法,其中部分方法还比较消耗性能。如果你试图优化查找进程相关方法的性能,可能本文分享的一些耗时数据可以作为参考。


 

性能比较

Process 类中提供了四种查询进程的方法:

  • GetProcesses

    • 获取当前计算机或远程计算机上运行的所有进程。
  • GetProcessById
    • 获取当前计算机或远程计算机上 pid 为 指定值的进程。
  • GetProcessesByName
    • 根据进程的名字查找当前计算机或远程计算机上的进程。
  • GetCurrentProcess
    • 获取当前进程的 Process 实例。

先给出我的实测数据(100 次执行耗时):

  • Process.GetProcesses()

    • 00:00:00.7254688
  • Process.GetProcessById(id)
    • 00:00:01.3660640(实际数值取决于当前进程数)
  • Process.GetProcessesByName("Walterlv.Demo")
    • 00:00:00.5604279
  • Process.GetCurrentProcess()
    • 00:00:00.0000546

结果显示获取所有进程实例的 GetProcesses 方法速度竟然比获取单个进程实例的 GetProcessById 还要快得多!额外地,根据名称查找进程比前两者都快,获取当前进程实例的方法快得不是一个数量级。

这些速度差异源于哪里

我们先看看最慢的方法 GetProcessIds,它的最本质的实现在 ProcessManager 类中:

  1. // ProcessManager
  2. public static int[] GetProcessIds() {
  3. int[] processIds = new int[256];
  4. int size;
  5. for (;;) {
  6. if (!NativeMethods.EnumProcesses(processIds, processIds.Length * 4, out size))
  7. throw new Win32Exception();
  8. if (size == processIds.Length * 4) {
  9. processIds = new int[processIds.Length * 2];
  10. continue;
  11. }
  12. break;
  13. }
  14. int[] ids = new int[size / 4];
  15. Array.Copy(processIds, ids, ids.Length);
  16. return ids;
  17. }

先创建一个 256 长度的数组,然后使用本机函数枚举进程列表填充这个数组。如果实际所需的数组大小与传入的数组大小相等,说明数组用完了,有可能进程数比 256 个多。所以,将数组长度扩大为两倍,随后再试一次。直到发现申请的数组长度足够存下进程数为止。

这里用到了本机方法 EnumProcesses 来枚举进程。传入的 size 要乘以 4 是因为传入的是字节数,一个 int 是 4 个字节。

  1. // NativeMethods
  2. [DllImport("psapi.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto, SetLastError=true)]
  3. public static extern bool EnumProcesses(int[] processIds, int size, out int needed);

所以我们可以得知,如果当前计算机中的进程数小于 256 个,那么枚举进程方法仅需执行一次;而如果大于或等于 256 个,则枚举进程的方法需要执行两次或更多次,这是性能很差的一个重要原因。

另外,GetProcesses 方法就要复杂得多,其核心调用的是 ProcessManager.GetProcessInfos 方法。方法很长,但其大体思路是获取当前计算机上的线程列表,然后将线程所在的进程储存到哈希表中(相当于去重),随后返回此哈希表的数组副本。

  1. // ProcessManager
  2. static ProcessInfo[] GetProcessInfos(IntPtr dataPtr, Predicate<int> processIdFilter) {
  3. // 60 is a reasonable number for processes on a normal machine.
  4. Hashtable processInfos = new Hashtable(60);
  5. long totalOffset = 0;
  6. while(true) {
  7. IntPtr currentPtr = (IntPtr)((long)dataPtr + totalOffset);
  8. SystemProcessInformation pi = new SystemProcessInformation();
  9. Marshal.PtrToStructure(currentPtr, pi);
  10. // Process ID shouldn't overflow. OS API GetCurrentProcessID returns DWORD.
  11. int processInfoProcessId = pi.UniqueProcessId.ToInt32();
  12. if (processIdFilter == null || processIdFilter(processInfoProcessId)) {
  13. // get information for a process
  14. ProcessInfo processInfo = new ProcessInfo();
  15. processInfo.processId = processInfoProcessId;
  16. processInfo.handleCount = (int)pi.HandleCount;
  17. processInfo.sessionId = (int)pi.SessionId;
  18. processInfo.poolPagedBytes = (long)pi.QuotaPagedPoolUsage;;
  19. processInfo.poolNonpagedBytes = (long)pi.QuotaNonPagedPoolUsage;
  20. processInfo.virtualBytes = (long)pi.VirtualSize;
  21. processInfo.virtualBytesPeak = (long)pi.PeakVirtualSize;
  22. processInfo.workingSetPeak = (long)pi.PeakWorkingSetSize;
  23. processInfo.workingSet = (long)pi.WorkingSetSize;
  24. processInfo.pageFileBytesPeak = (long)pi.PeakPagefileUsage;
  25. processInfo.pageFileBytes = (long)pi.PagefileUsage;
  26. processInfo.privateBytes = (long)pi.PrivatePageCount;
  27. processInfo.basePriority = pi.BasePriority;
  28. if( pi.NamePtr == IntPtr.Zero) {
  29. if( processInfo.processId == NtProcessManager.SystemProcessID) {
  30. processInfo.processName = "System";
  31. }
  32. else if( processInfo.processId == NtProcessManager.IdleProcessID) {
  33. processInfo.processName = "Idle";
  34. }
  35. else {
  36. // for normal process without name, using the process ID.
  37. processInfo.processName = processInfo.processId.ToString(CultureInfo.InvariantCulture);
  38. }
  39. }
  40. else {
  41. string processName = GetProcessShortName(Marshal.PtrToStringUni(pi.NamePtr, pi.NameLength/sizeof(char)));
  42. //
  43. // On old operating system (NT4 and windows 2000), the process name might be truncated to 15
  44. // characters. For example, aspnet_admin.exe will show up in performance counter as aspnet_admin.ex.
  45. // Process class try to return a nicer name. We used to get the main module name for a process and
  46. // use that as the process name. However normal user doesn't have access to module information,
  47. // so normal user will see an exception when we try to get a truncated process name.
  48. //
  49. if (ProcessManager.IsOSOlderThanXP && (processName.Length == 15)) {
  50. if (processName.EndsWith(".", StringComparison.OrdinalIgnoreCase)) {
  51. processName = processName.Substring(0, 14);
  52. }
  53. else if (processName.EndsWith(".e", StringComparison.OrdinalIgnoreCase)) {
  54. processName = processName.Substring(0, 13);
  55. }
  56. else if (processName.EndsWith(".ex", StringComparison.OrdinalIgnoreCase)) {
  57. processName = processName.Substring(0, 12);
  58. }
  59. }
  60. processInfo.processName = processName;
  61. }
  62. // get the threads for current process
  63. processInfos[processInfo.processId] = processInfo;
  64. currentPtr = (IntPtr)((long)currentPtr + Marshal.SizeOf(pi));
  65. int i = 0;
  66. while( i < pi.NumberOfThreads) {
  67. SystemThreadInformation ti = new SystemThreadInformation();
  68. Marshal.PtrToStructure(currentPtr, ti);
  69. ThreadInfo threadInfo = new ThreadInfo();
  70. threadInfo.processId = (int)ti.UniqueProcess;
  71. threadInfo.threadId = (int)ti.UniqueThread;
  72. threadInfo.basePriority = ti.BasePriority;
  73. threadInfo.currentPriority = ti.Priority;
  74. threadInfo.startAddress = ti.StartAddress;
  75. threadInfo.threadState = (ThreadState)ti.ThreadState;
  76. threadInfo.threadWaitReason = NtProcessManager.GetThreadWaitReason((int)ti.WaitReason);
  77. processInfo.threadInfoList.Add(threadInfo);
  78. currentPtr = (IntPtr)((long)currentPtr + Marshal.SizeOf(ti));
  79. i++;
  80. }
  81. }
  82. if (pi.NextEntryOffset == 0) {
  83. break;
  84. }
  85. totalOffset += pi.NextEntryOffset;
  86. }
  87. ProcessInfo[] temp = new ProcessInfo[processInfos.Values.Count];
  88. processInfos.Values.CopyTo(temp, 0);
  89. return temp;
  90. }

GetProcessesByName 方法就比较奇怪了,因为其本质上就是调用了 Process.GetProcesses 方法,并在其后额外执行了一些代码。理论上不应该出现耗时更短的情况。事实上,在测试中,我将 GetProcessesGetProcessesByName 方法的执行调换顺序也能得到稳定一致的结果,都是 GetProcessesByName 更快。

  1. public static Process[] GetProcessesByName(string processName, string machineName) {
  2. if (processName == null) processName = String.Empty;
  3. Process[] procs = GetProcesses(machineName);
  4. ArrayList list = new ArrayList();
  5. for(int i = 0; i < procs.Length; i++) {
  6. if( String.Equals(processName, procs[i].ProcessName, StringComparison.OrdinalIgnoreCase)) {
  7. list.Add( procs[i]);
  8. } else {
  9. procs[i].Dispose();
  10. }
  11. }
  12. Process[] temp = new Process[list.Count];
  13. list.CopyTo(temp, 0);
  14. return temp;
  15. }

至于 GetCurrentProcess 方法能够这么快,很好理解,毕竟是自己进程,有什么拿不到的呢?其内部调用的是本机方法:

  1. [DllImport("kernel32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)]
  2. public static extern int GetCurrentProcessId();

另外,有个有意思的现象:

.NET 中 GetProcess 相关方法的性能的更多相关文章

  1. Linux中查找最耗性能的JAVA代码

    在这里总结一下查找Linux.Java环境下最耗CPU性能的代码段的方法.基本上原理就是使用top命令查看最耗cpu的进程和线程(子进程).使用jstack把java线程堆栈给dump下来.然后,在堆 ...

  2. Oracle中HWM与数据库性能的探讨

    Oracle中HWM与数据库性能的探讨 一.什么是高水位 HWM(high water mark),高水标记,这个概念在segment的存储内容中是比较重要的.简单来说,HWM就是一个segment中 ...

  3. 品味性能之道<十一>:JAVA中switch和if性能比较

    通常而言大家普遍的认知里switch case的效率高于if else.根据我的理解而言switch的查找类似于二叉树,if则是线性查找.按照此逻辑推理对于对比条件数目大于3时switch更优,并且对 ...

  4. sar命令,linux中最为全面的性能分析工具之一

    sar是System Activity Reporter(系统活动情况报告)的缩写.这个工具所需要的负载很小,也是目前linux中最为全面的性能分析工具之一.此款工具将对系统当前的状态就行取样,然后通 ...

  5. 【转】PHP中被忽略的性能优化利器:生成器.md

      PHP  如果是做Python或者其他语言的小伙伴,对于生成器应该不陌生.但很多PHP开发者或许都不知道生成器这个功能,可能是因为生成器是PHP 5.5.0才引入的功能,也可以是生成器作用不是很明 ...

  6. Mysql中Union和OR性能对比

    博客已搬家,更多内容查看https://liangyongrui.github.io/ Mysql中Union和OR性能对比 在leetcode上看到一篇文章,整理一下 参考:https://leet ...

  7. Linux中常用的监控性能的命令(sar、mpstat,vmstat, iostat,)详解

    Linux中常用的监控性能的命令有: sar:能查看CPU的平均信息,还能查看指定CPU的信息.与mpstat相比,sar能查看CPU历史信息 mpstat:能查看所有CPU的平均信息,还能查看指定C ...

  8. C#中那些[举手之劳]的性能优化

    隔了很久没写东西了,主要是最近比较忙,更主要的是最近比较懒...... 其实这篇很早就想写了 工作和生活中经常可以看到一些程序猿,写代码的时候只关注代码的逻辑性,而不考虑运行效率 其实这对大多数程序猿 ...

  9. ASP.NET中常用的优化性能的方法

    1. 数据库访问性能优化 数据库的连接和关闭 访问数据库资源需要创建连接.打开连接和关闭连接几个操作.这些过程需要多次与数据库交换信息以通过身份验证,比较耗费服务器资源.ASP.NET中提供了连接池( ...

随机推荐

  1. AJAX的例子

    var XMLHttpReq; //根据不同的浏览器创建不同的XMLHttpRequest对象function createXMLHttpRequest() {  if (window.XMLHttp ...

  2. 深入浅出-Binding的源与路径

    1.把控件作为Binding源与Binding标记扩展<TextBox x:Name="textBox1" Text="{Binding Path=Value, E ...

  3. TestNG,多个场景结合运行Suite.xml

    方法一.首先新增一个.xml文件(经过一段时间的练习,找到其他方法添加XML,如下) 再到文件中添加如下: <suite name = "Selenium school"&g ...

  4. 【Demo】jQuery 轮播图简单动画效果

    功能实现: (1)设定图片称号的鼠标悬停事件: (2)在事件中利用自定义动画函数调整显示图片,并修改对应标号样式: (3)为图片显示区域设定鼠标悬停事件: (4)当鼠标停在该区域时,清除图片切换动画定 ...

  5. java之JDBC多条语句执行

    在开发过程中,有时我们需要执行多条SQL语句,那如何处理才能解决这样的问题? 1,多条语句执行错误 原因:试图用一个PreparedStatement对象,执行多次SQL操作.程序会提示一下错误: O ...

  6. UVA-11478 Halum (差分约束系统)

    题目大意:一张n个节点的有向带边权图,每次操作能任选一个节点v个一个整数d,使以v为终点的边权值都减少d,以v为起点的边权值都增加d,求若干次操作后的最小边权值的非负最大值. 题目分析:用sum[i] ...

  7. IOS-如何锁定Xcode的API头文件

    如何锁定Xcode的API头文件1, 打开终端2, 前往Xcode.app, 命令: cd /Applications/Xcode.app3, 把头文件修改为只读, 命令: sudo chown -h ...

  8. datagrid与DropDownList关联使用

    最近做一个页面需要用到这个两个控件,之前虽然看过,但是没有动手实践过.突然要做这么一个页面,并用上,真的有点着急.于是乎,网上疯狂找datagrid与DropDownList 的例子,找了很多很多,看 ...

  9. LeetCode OJ:Reorder List(重序链表)

    Given a singly linked list L: L0→L1→…→Ln-1→Ln,reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→… You must do thi ...

  10. Python3 数字Number(六)

    Python 数字数据类型用于存储数值. 数据类型是不允许改变的,这就意味着如果改变数字数据类型得值,将重新分配内存空间. 以下实例在变量赋值时 Number 对象将被创建: var1 = 1 var ...