.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 类中:

// ProcessManager
public static int[] GetProcessIds() {
int[] processIds = new int[256];
int size;
for (;;) {
if (!NativeMethods.EnumProcesses(processIds, processIds.Length * 4, out size))
throw new Win32Exception();
if (size == processIds.Length * 4) {
processIds = new int[processIds.Length * 2];
continue;
}
break;
}
int[] ids = new int[size / 4];
Array.Copy(processIds, ids, ids.Length);
return ids;
}

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

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

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

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

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

// ProcessManager
static ProcessInfo[] GetProcessInfos(IntPtr dataPtr, Predicate<int> processIdFilter) {
// 60 is a reasonable number for processes on a normal machine.
Hashtable processInfos = new Hashtable(60); long totalOffset = 0; while(true) {
IntPtr currentPtr = (IntPtr)((long)dataPtr + totalOffset);
SystemProcessInformation pi = new SystemProcessInformation(); Marshal.PtrToStructure(currentPtr, pi); // Process ID shouldn't overflow. OS API GetCurrentProcessID returns DWORD.
int processInfoProcessId = pi.UniqueProcessId.ToInt32(); if (processIdFilter == null || processIdFilter(processInfoProcessId)) {
// get information for a process
ProcessInfo processInfo = new ProcessInfo();
processInfo.processId = processInfoProcessId;
processInfo.handleCount = (int)pi.HandleCount;
processInfo.sessionId = (int)pi.SessionId;
processInfo.poolPagedBytes = (long)pi.QuotaPagedPoolUsage;;
processInfo.poolNonpagedBytes = (long)pi.QuotaNonPagedPoolUsage;
processInfo.virtualBytes = (long)pi.VirtualSize;
processInfo.virtualBytesPeak = (long)pi.PeakVirtualSize;
processInfo.workingSetPeak = (long)pi.PeakWorkingSetSize;
processInfo.workingSet = (long)pi.WorkingSetSize;
processInfo.pageFileBytesPeak = (long)pi.PeakPagefileUsage;
processInfo.pageFileBytes = (long)pi.PagefileUsage;
processInfo.privateBytes = (long)pi.PrivatePageCount;
processInfo.basePriority = pi.BasePriority; if( pi.NamePtr == IntPtr.Zero) {
if( processInfo.processId == NtProcessManager.SystemProcessID) {
processInfo.processName = "System";
}
else if( processInfo.processId == NtProcessManager.IdleProcessID) {
processInfo.processName = "Idle";
}
else {
// for normal process without name, using the process ID.
processInfo.processName = processInfo.processId.ToString(CultureInfo.InvariantCulture);
}
}
else {
string processName = GetProcessShortName(Marshal.PtrToStringUni(pi.NamePtr, pi.NameLength/sizeof(char)));
//
// On old operating system (NT4 and windows 2000), the process name might be truncated to 15
// characters. For example, aspnet_admin.exe will show up in performance counter as aspnet_admin.ex.
// Process class try to return a nicer name. We used to get the main module name for a process and
// use that as the process name. However normal user doesn't have access to module information,
// so normal user will see an exception when we try to get a truncated process name.
//
if (ProcessManager.IsOSOlderThanXP && (processName.Length == 15)) {
if (processName.EndsWith(".", StringComparison.OrdinalIgnoreCase)) {
processName = processName.Substring(0, 14);
}
else if (processName.EndsWith(".e", StringComparison.OrdinalIgnoreCase)) {
processName = processName.Substring(0, 13);
}
else if (processName.EndsWith(".ex", StringComparison.OrdinalIgnoreCase)) {
processName = processName.Substring(0, 12);
}
}
processInfo.processName = processName;
} // get the threads for current process
processInfos[processInfo.processId] = processInfo; currentPtr = (IntPtr)((long)currentPtr + Marshal.SizeOf(pi));
int i = 0;
while( i < pi.NumberOfThreads) {
SystemThreadInformation ti = new SystemThreadInformation();
Marshal.PtrToStructure(currentPtr, ti);
ThreadInfo threadInfo = new ThreadInfo(); threadInfo.processId = (int)ti.UniqueProcess;
threadInfo.threadId = (int)ti.UniqueThread;
threadInfo.basePriority = ti.BasePriority;
threadInfo.currentPriority = ti.Priority;
threadInfo.startAddress = ti.StartAddress;
threadInfo.threadState = (ThreadState)ti.ThreadState;
threadInfo.threadWaitReason = NtProcessManager.GetThreadWaitReason((int)ti.WaitReason); processInfo.threadInfoList.Add(threadInfo);
currentPtr = (IntPtr)((long)currentPtr + Marshal.SizeOf(ti));
i++;
}
} if (pi.NextEntryOffset == 0) {
break;
}
totalOffset += pi.NextEntryOffset;
} ProcessInfo[] temp = new ProcessInfo[processInfos.Values.Count];
processInfos.Values.CopyTo(temp, 0);
return temp;
}

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

public static Process[] GetProcessesByName(string processName, string machineName) {
if (processName == null) processName = String.Empty;
Process[] procs = GetProcesses(machineName);
ArrayList list = new ArrayList(); for(int i = 0; i < procs.Length; i++) {
if( String.Equals(processName, procs[i].ProcessName, StringComparison.OrdinalIgnoreCase)) {
list.Add( procs[i]);
} else {
procs[i].Dispose();
}
} Process[] temp = new Process[list.Count];
list.CopyTo(temp, 0);
return temp;
}

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

[DllImport("kernel32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)]
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. python的变量,对象的内存地址以及参数传递过程

    作为一个由c/c++转过来的菜鸟,刚接触Python的变量的时候很不适应,应为他的行为很像指针,void* ,不知道大家有没有这样的感觉.其实Python是以数据为本,变量可以理解为标签.作为c/c+ ...

  2. ACM ICPC 2010–2011, Northeastern European Regional Contest St Petersburg – Barnaul – Tashkent – Tbilisi, November 24, 2010

    ACM ICPC 2010–2011, Northeastern European Regional Contest St Petersburg – Barnaul – Tashkent – Tbil ...

  3. [sping]xml配置文件中factory-bean与factory-method(spring使用工厂方法注入bean)

    public class CarFactory { //非静态方法 public Car createCar(){ Car car = new Car(); car.setBrand("BM ...

  4. 英语每日写作---4、VOA慢速英语(翻译+字幕+讲解):专家:城市发展将加剧住房危机

    英语每日写作---4.VOA慢速英语(翻译+字幕+讲解):专家:城市发展将加剧住房危机 一.总结 一句话总结: takes place 发生deal with 处理:应付population grow ...

  5. 面向目标的场景设置--Goal-Oriented Scenario

    在场景设置的时候会有两种场景设置方式: 1,手动模式(Manual Scenario) 2.面向目标的场景设置模式(Goal Oriented scenario) 其中手动模式使用较多,而且灵活应用, ...

  6. findContours函数参数详解

    http://blog.csdn.net/dcrmg/article/details/51987348

  7. Linux命令详解-hwclock/chock

    hwclock命令可以用来显示/设置硬件时钟命令. 在Linux中有硬件时钟与系统时钟等两种时钟.硬件时钟是指主机板上的时钟设备,也就是通常可在BIOS画面设定的时钟.系统时钟则是指kernel中 的 ...

  8. 第七天 Linux用户管理、RHEL6.5及RHEL7.2 root密码破解、RHEL6.5安装vmware tools

    1.Linux用户管理 Linux系统中,存在三种用户 A.超级用户:root 最高权限,至高无上 在windows中 administrator是可以登录的最高权限,但是,system权限最高,不能 ...

  9. Lua学习笔记1,基本数据类型

    1.字符串的连接使用的是  .. ,如 print(123 .. 44) 输出 12344 print ('a' .. 'b') 输出 ab print(123 .. 44)这句的时候 .. 两边要空 ...

  10. bzoj 1996 区间dp

    1996: [Hnoi2010]chorus 合唱队 Time Limit: 4 Sec  Memory Limit: 64 MBSubmit: 1727  Solved: 1115[Submit][ ...