如何通过.NET/C#找到CPU核心数?

356

有没有一种通过 .NET/C# 方法来找出 CPU 核心数的方式?

备注:这是一个纯粹的代码问题,而不是一个“我应该使用多线程?”的问题! :-)


8
你需要知道有多少个核心或逻辑处理器吗?仅仅运行多个线程,两者都可能足够,但在某些情况下,差异可能很重要。 - Kevin Kibler
有没有更新的方法来做这件事? - MoonKnight
11个回答

518

有几个与处理器相关的不同信息:

  1. 物理处理器数量
  2. 内核数量
  3. 逻辑处理器数量。

这些可能都不同;对于具有2个双核心超线程启用处理器的机器,有2个物理处理器,4个内核和8个逻辑处理器。

逻辑处理器的数量可通过Environment类获得,但其他信息只能通过WMI获得(在某些系统上,您可能需要安装一些热修复程序或服务包):

确保在项目中添加对System.Management.dll的引用 在.NET Core中,这可作为NuGet软件包提供(仅限于Windows)。

物理处理器:

foreach (var item in new System.Management.ManagementObjectSearcher("Select * from Win32_ComputerSystem").Get())
{
    Console.WriteLine("Number Of Physical Processors: {0} ", item["NumberOfProcessors"]);
}

核心:

int coreCount = 0;
foreach (var item in new System.Management.ManagementObjectSearcher("Select * from Win32_Processor").Get())
{
    coreCount += int.Parse(item["NumberOfCores"].ToString());
}
Console.WriteLine("Number Of Cores: {0}", coreCount);

逻辑处理器:

Console.WriteLine("Number Of Logical Processors: {0}", Environment.ProcessorCount);

或者

foreach (var item in new System.Management.ManagementObjectSearcher("Select * from Win32_ComputerSystem").Get())
{
    Console.WriteLine("Number Of Logical Processors: {0}", item["NumberOfLogicalProcessors"]);
}

被Windows排除的处理器:

你还可以使用 setupapi.dll 中的 Windows API 调用来发现已被 Windows 排除(例如通过引导设置)且无法使用上述方法检测到的处理器。下面的代码给出了存在的逻辑处理器总数(我尚未能够区分物理和逻辑处理器),包括那些被 Windows 排除的处理器:

static void Main(string[] args)
{
    int deviceCount = 0;
    IntPtr deviceList = IntPtr.Zero;
    // GUID for processor classid
    Guid processorGuid = new Guid("{50127dc3-0f36-415e-a6cc-4cb3be910b65}");

    try
    {
        // get a list of all processor devices
        deviceList = SetupDiGetClassDevs(ref processorGuid, "ACPI", IntPtr.Zero, (int)DIGCF.PRESENT);
        // attempt to process each item in the list
        for (int deviceNumber = 0; ; deviceNumber++)
        {
            SP_DEVINFO_DATA deviceInfo = new SP_DEVINFO_DATA();
            deviceInfo.cbSize = Marshal.SizeOf(deviceInfo);

            // attempt to read the device info from the list, if this fails, we're at the end of the list
            if (!SetupDiEnumDeviceInfo(deviceList, deviceNumber, ref deviceInfo))
            {
                deviceCount = deviceNumber;
                break;
            }
        }
    }
    finally
    {
        if (deviceList != IntPtr.Zero) { SetupDiDestroyDeviceInfoList(deviceList); }
    }
    Console.WriteLine("Number of cores: {0}", deviceCount);
}

[DllImport("setupapi.dll", SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid,
    [MarshalAs(UnmanagedType.LPStr)]String enumerator,
    IntPtr hwndParent,
    Int32 Flags);

[DllImport("setupapi.dll", SetLastError = true)]
private static extern Int32 SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);

[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet,
    Int32 MemberIndex,
    ref SP_DEVINFO_DATA DeviceInterfaceData);

[StructLayout(LayoutKind.Sequential)]
private struct SP_DEVINFO_DATA
{
    public int cbSize;
    public Guid ClassGuid;
    public uint DevInst;
    public IntPtr Reserved;
}

private enum DIGCF
{
    DEFAULT = 0x1,
    PRESENT = 0x2,
    ALLCLASSES = 0x4,
    PROFILE = 0x8,
    DEVICEINTERFACE = 0x10,
}

5
WMI代码生成器可以帮助发现值并创建查询(它甚至可以生成C#/VB.NET的存根)。 - StingyJack
4
它在 System.Management.dll 中。你的项目中是否包含对该程序集的引用? - Kevin Kibler
3
上述代码存在一个小的偏移问题。因为deviceCount是从0开始计数的,所以核心数量应该像这样输出:Console.WriteLine("Number of cores: {0}", deviceCount + 1); - Francis Litterio
4
不及时处理管理对象和搜索器会导致问题,你觉得这样做有风险吗? - Benjamin
1
我的系统是Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz,3401 Mhz,4个核心,8个逻辑处理器。上面的代码返回7... - Vlad
显示剩余6条评论

233

18
太简单了,让我几乎要流泪了。谢谢你的回复! - MrGreggles
80
这提供的是逻辑处理器数量,而不是核心数量。 - Kevin Kibler
10
从这个问题来看,我怀疑提问者不理解差异,如果你也不知道差异,那么这可能是你想要的答案。 - Glenn Maynard
1
这也会在许多核心系统上返回错误的计数。我正在运行两个具有超线程的十二核处理器,总共有48个逻辑处理器。Environment.ProcessorCount只返回32个。 - Allen Clark Copeland Jr
1
@AlexanderMorou,是的,在一些多CPU服务器上,这种方法无法提供准确的结果。有一个解决方法,但我还没有测试过。 - TheLegendaryCopyCoder
1
这将返回当前进程可用的逻辑处理器。如果使用亲和性,则结果可能会有所不同! - Engin

38

WMI查询速度较慢,因此请尽量只选择所需成员而不是使用Select *。

以下查询需要3.4秒:

foreach (var item in new System.Management.ManagementObjectSearcher("Select * from Win32_Processor").Get())

虽然这个只需要0.122秒:

foreach (var item in new System.Management.ManagementObjectSearcher("Select NumberOfCores from Win32_Processor").Get())

1
你在哪个系统上运行这个程序?我使用多个“Select *”查询,而且它根本不需要3.4秒,已经在我软件部署的数千台计算机上进行了测试。我使用“Select *”是因为我需要从对象中获取多个属性。但是,我的做法有点不同:创建一个“Select *”的ObjectQuery;获取ManagementObjectCollection;然后对于ManagementObjectCollection中的每个ManagementObject执行操作。 - deegee
@deegee:你说得对,“Select *”查询本身并不需要太长时间,只是如果迭代返回的所有值而不仅仅是NumberOfCores,则下面的int解析会变慢。 - Aleix Mercader

19

62
这个值表示逻辑处理器的数量,而非核心的数量。 - Kevin Kibler

10

最简单的方法是 = Environment.ProcessorCount
来自Environment.ProcessorCount属性的示例。

using System;

class Sample 
{
    public static void Main() 
    {
        Console.WriteLine("The number of processors " +
            "on this computer is {0}.", 
            Environment.ProcessorCount);
    }
}

方法 Environment.ProcessorCount 有时会显示不正确的数据(请参见 https://dev59.com/2F4c5IYBdhLWcg3wY5mW) - constructor
另外三个答案,包括被接受的答案,已经建议使用Environment.ProcessorCount - Lance U. Matthews

9

看到.NET内部是如何实现的确实很有趣...它的实现方式非常“简单”,如下:

namespace System.Threading
{
    using System;
    using System.Runtime.CompilerServices;

    internal static class PlatformHelper
    {
        private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 0x7530;
        private static volatile int s_lastProcessorCountRefreshTicks;
        private static volatile int s_processorCount;

        internal static bool IsSingleProcessor
        {
            get
            {
                return (ProcessorCount == 1);
            }
        }

        internal static int ProcessorCount
        {
            get
            {
                int tickCount = Environment.TickCount;
                int num2 = s_processorCount;
                if ((num2 == 0) || ((tickCount - s_lastProcessorCountRefreshTicks) >= 0x7530))
                {
                    s_processorCount = num2 = Environment.ProcessorCount;
                    s_lastProcessorCountRefreshTicks = tickCount;
                }
                return num2;
            }
        }
    }
}

1
但这真的很有意思吗?它只是在一段时间内缓存了Environment.ProcessorCount的值,并且仅由某些同步相关类internal使用。如何获取Environment.ProcessorCount的值才是有信息量的。此外,看起来您是从反编译器而不是源代码获取此代码。 - Lance U. Matthews

4

来自 .NET Framework 源代码

你也可以通过在 Kernel32.dll 上使用PInvoke 来获取它。

以下代码基本上来自于 System.Web 源代码中的 SystemInfo.cs,位于这里

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SYSTEM_INFO
{
  public ushort wProcessorArchitecture;
  public ushort wReserved;
  public uint dwPageSize;
  public IntPtr lpMinimumApplicationAddress;
  public IntPtr lpMaximumApplicationAddress;
  public IntPtr dwActiveProcessorMask;
  public uint dwNumberOfProcessors;
  public uint dwProcessorType;
  public uint dwAllocationGranularity;
  public ushort wProcessorLevel;
  public ushort wProcessorRevision;
}

internal static class SystemInfo 
{
    static int _trueNumberOfProcessors;
    internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);    

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    internal static extern void GetSystemInfo(out SYSTEM_INFO si);

    [DllImport("kernel32.dll")]
    internal static extern int GetProcessAffinityMask(IntPtr handle, out IntPtr processAffinityMask, out IntPtr systemAffinityMask);

    internal static int GetNumProcessCPUs()
    {
      if (SystemInfo._trueNumberOfProcessors == 0)
      {
        SYSTEM_INFO si;
        GetSystemInfo(out si);
        if ((int) si.dwNumberOfProcessors == 1)
        {
          SystemInfo._trueNumberOfProcessors = 1;
        }
        else
        {
          IntPtr processAffinityMask;
          IntPtr systemAffinityMask;
          if (GetProcessAffinityMask(INVALID_HANDLE_VALUE, out processAffinityMask, out systemAffinityMask) == 0)
          {
            SystemInfo._trueNumberOfProcessors = 1;
          }
          else
          {
            int num1 = 0;
            if (IntPtr.Size == 4)
            {
              uint num2 = (uint) (int) processAffinityMask;
              while ((int) num2 != 0)
              {
                if (((int) num2 & 1) == 1)
                  ++num1;
                num2 >>= 1;
              }
            }
            else
            {
              ulong num2 = (ulong) (long) processAffinityMask;
              while ((long) num2 != 0L)
              {
                if (((long) num2 & 1L) == 1L)
                  ++num1;
                num2 >>= 1;
              }
            }
            SystemInfo._trueNumberOfProcessors = num1;
          }
        }
      }
      return SystemInfo._trueNumberOfProcessors;
    }
}

3
尝试了这个,但它返回逻辑处理器的数量 - 这与调用 Environment.ProcessorCount 的结果相同。 - Bob Bryan

3

这里已经有很多答案了,但有些答案得到了大量的赞,但是是不正确的。

.NET Environment.ProcessorCount会返回错误的值,如果您的系统WMI配置不正确,可能会导致严重失败。

如果您想要一个可靠的方法来计算核心数,唯一的方法是使用Win32 API。

下面是一个C++代码片段:

#include <Windows.h>
#include <vector>

int num_physical_cores()
{
    static int num_cores = []
    {
        DWORD bytes = 0;
        GetLogicalProcessorInformation(nullptr, &bytes);
        std::vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> coreInfo(bytes / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION));
        GetLogicalProcessorInformation(coreInfo.data(), &bytes);

        int cores = 0;
        for (auto& info : coreInfo)
        {
            if (info.Relationship == RelationProcessorCore)
                ++cores;
        }
        return cores > 0 ? cores : 1;
    }();
    return num_cores;
}

既然这是一个.NET C#问题,这里是转换后的版本:

[StructLayout(LayoutKind.Sequential)]
struct CACHE_DESCRIPTOR
{
    public byte Level;
    public byte Associativity;
    public ushort LineSize;
    public uint Size;
    public uint Type;
}

[StructLayout(LayoutKind.Explicit)]
struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION_UNION
{
    [FieldOffset(0)] public byte ProcessorCore;
    [FieldOffset(0)] public uint NumaNode;
    [FieldOffset(0)] public CACHE_DESCRIPTOR Cache;
    [FieldOffset(0)] private UInt64 Reserved1;
    [FieldOffset(8)] private UInt64 Reserved2;
}

public enum LOGICAL_PROCESSOR_RELATIONSHIP
{
    RelationProcessorCore,
    RelationNumaNode,
    RelationCache,
    RelationProcessorPackage,
    RelationGroup,
    RelationAll = 0xffff
}

struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION
{
    public UIntPtr ProcessorMask;
    public LOGICAL_PROCESSOR_RELATIONSHIP Relationship;
    public SYSTEM_LOGICAL_PROCESSOR_INFORMATION_UNION ProcessorInformation;
}

[DllImport("kernel32.dll")]
static extern unsafe bool GetLogicalProcessorInformation(SYSTEM_LOGICAL_PROCESSOR_INFORMATION* buffer, out int bufferSize);

static unsafe int GetProcessorCoreCount()
{
    GetLogicalProcessorInformation(null, out int bufferSize);
    int numEntries = bufferSize / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
    var coreInfo = new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[numEntries];

    fixed (SYSTEM_LOGICAL_PROCESSOR_INFORMATION* pCoreInfo = coreInfo)
    {
        GetLogicalProcessorInformation(pCoreInfo, out bufferSize);
        int cores = 0;
        for (int i = 0; i < numEntries; ++i)
        {
            ref SYSTEM_LOGICAL_PROCESSOR_INFORMATION info = ref pCoreInfo[i];
            if (info.Relationship == LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore)
                ++cores;
        }
        return cores > 0 ? cores : 1;
    }
}

public static readonly int NumPhysicalCores = GetProcessorCoreCount();

是否可以获取物理处理器数量和逻辑处理器数量? - CTZStef

1

一种选择是从注册表中读取数据。 有关此主题的MSDN文章:http://msdn.microsoft.com/en-us/library/microsoft.win32.registry.localmachine(v=vs.71).aspx)

我相信处理器可以在此处找到,HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor

    private void determineNumberOfProcessCores()
    {
        RegistryKey rk = Registry.LocalMachine;
        String[] subKeys = rk.OpenSubKey("HARDWARE").OpenSubKey("DESCRIPTION").OpenSubKey("System").OpenSubKey("CentralProcessor").GetSubKeyNames();

        textBox1.Text = "Total number of cores:" + subKeys.Length.ToString();
    }

我相信大多数系统都会有这个注册表项。
我想分享我的意见。

这将给出已经在Environment.ProcessorCount中可用的处理器数量,是否有其他类似的方法来获取每个处理器的核心数? - Armen

0
你可以使用这个类:
public static class CpuCores
{
    private static int cores = 0;
    
    public static int Number
    { 
        get
        {
            if (cores > 0) return cores;

            RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Class\" +
                "{50127dc3-0f36-415e-a6cc-4cb3be910b65}");
            if (key == null)
            {
                cores = Environment.ProcessorCount;
                return cores;
            }
            string[] subkeys = key.GetSubKeyNames();
            key.Close();
            cores = 0;
            if (subkeys != null && subkeys.Length > 0) foreach (string s in subkeys)
            {
                if (s.Length != 4) continue;
                int n;
                if (int.TryParse(s, out n) && ++n > cores) cores = n;
            }
            if (cores <= 0) cores = Environment.ProcessorCount;
            return cores;
        } 
    }
}

阅读注册表有什么优势吗?如果你最终只是要回退到 Environment.ProcessorCount,那为什么不一开始就直接使用它呢? - Lance U. Matthews

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接