如何检测Windows、Mac和Linux系统上的物理处理器/核心数量?

53

我有一个多线程的C++应用程序,在Windows、Mac和几个Linux版本上运行。

简单地说,为了使其以最大效率运行,我必须能够针对每个物理处理器/核心实例化一个单独的线程。创建超过物理处理器/核心数量的线程会严重降低我的程序性能。在这三个平台上,我已经能够正确检测到逻辑处理器/核心的数量。要能够正确检测到物理处理器/核心的数量,我将不得不检测超线程是否被支持并处于活动状态。

因此,我的问题是是否有一种方法可以检测超线程是否受支持并启用?如果是,请说明如何检测。


1
请查看此链接 - https://dev59.com/EXVC5IYBdhLWcg3w51lv - Secko
1
你不是几天前已经问过同样的问题了吗?https://dev59.com/Zk7Sa4cB1Zd3GeqPzwKR - Ken Bloom
你放弃了这个问题吗? - jcoffland
你是否已经成功解决了这个问题?还需要帮助吗? - jcolebrand
目前正在开发一个跨平台的解决方案,用于访问硬件/系统信息:https://github.com/lfreist/hwinfo - prog2de
14个回答

31

编辑:由于英特尔持续的困惑,这不再是100%正确的。

我理解你的问题是要检测CPU核心数和CPU线程数的区别,这与检测系统中逻辑核心数和物理核心数不同。除非拥有自己的封装或芯片,否则操作系统通常不会将CPU核心视为物理核心。因此,例如,一个 Core 2 Duo 在性能方面与具有超线程技术的 Intel P4 报告相同,即使 2 个超线程与 2 个 CPU 核心在性能上是非常不同的。

我一直在努力研究这个问题,直到我拼凑出以下解决方案,我认为它适用于 AMD 和 Intel 处理器。据我所知,虽然可能有错,但 AMD 目前还没有 CPU 线程,但他们已提供了一种检测方式,我认为这种方式在将来可能拥有 CPU 线程的 AMD 处理器上也有效。

简而言之,以下是使用 CPUID 指令的步骤:

  1. 使用 CPUID 函数 0 检测 CPU 厂商
  2. 从 CPUID 函数 1 的 CPU 功能 EDX 中检查 HTT 位 28
  3. 从 CPUID 函数 1 的 EBX [23:16] 获取逻辑核心计数
  4. 获取实际非线程 CPU 核心计数
    1. 如果厂商 ==“ GenuineIntel”,则从 CPUID 函数 4 的 EAX [31:26] 加 1
    2. 如果厂商 ==“ AuthenticAMD”,则从 CPUID 函数 0x80000008 的 ECX [7:0] 加 1

听起来很困难,但这里是一个希望能够跨平台的 C++ 程序,可以完成此操作:

#include <iostream>
#include <string>

using namespace std;


void cpuID(unsigned i, unsigned regs[4]) {
#ifdef _WIN32
  __cpuid((int *)regs, (int)i);

#else
  asm volatile
    ("cpuid" : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3])
     : "a" (i), "c" (0));
  // ECX is set to zero for CPUID function 4
#endif
}


int main(int argc, char *argv[]) {
  unsigned regs[4];

  // Get vendor
  char vendor[12];
  cpuID(0, regs);
  ((unsigned *)vendor)[0] = regs[1]; // EBX
  ((unsigned *)vendor)[1] = regs[3]; // EDX
  ((unsigned *)vendor)[2] = regs[2]; // ECX
  string cpuVendor = string(vendor, 12);

  // Get CPU features
  cpuID(1, regs);
  unsigned cpuFeatures = regs[3]; // EDX

  // Logical core count per CPU
  cpuID(1, regs);
  unsigned logical = (regs[1] >> 16) & 0xff; // EBX[23:16]
  cout << " logical cpus: " << logical << endl;
  unsigned cores = logical;

  if (cpuVendor == "GenuineIntel") {
    // Get DCP cache info
    cpuID(4, regs);
    cores = ((regs[0] >> 26) & 0x3f) + 1; // EAX[31:26] + 1

  } else if (cpuVendor == "AuthenticAMD") {
    // Get NC: Number of CPU cores - 1
    cpuID(0x80000008, regs);
    cores = ((unsigned)(regs[2] & 0xff)) + 1; // ECX[7:0] + 1
  }

  cout << "    cpu cores: " << cores << endl;

  // Detect hyper-threads  
  bool hyperThreads = cpuFeatures & (1 << 28) && cores < logical;

  cout << "hyper-threads: " << (hyperThreads ? "true" : "false") << endl;

  return 0;
}

我还没有在Windows或OSX上测试过这个,但是它应该可以工作,因为CPUID指令在i686机器上是有效的。显然,这对于PowerPC来说行不通,但是它们也没有超线程。

以下是几台不同Intel机器的输出:

Intel(R) Core(TM)2 Duo CPU T7500 @ 2.20GHz:

 logical cpus: 2
    cpu cores: 2
hyper-threads: false

Intel(R) Core(TM)2 Quad CPU Q8400 @ 2.66GHz:

 logical cpus: 4
    cpu cores: 4
hyper-threads: false

Intel(R) Xeon(R) CPU E5520 @ 2.27GHz(带有x2物理CPU包):

 logical cpus: 16
    cpu cores: 8
hyper-threads: true

英特尔(R)奔腾(R) 4处理器3.00GHz:

 logical cpus: 2
    cpu cores: 1
hyper-threads: true

7
你能详细说明一下吗? - jcoffland
4
@Alex,上面例子中的Xeon E5520来自一台有两个处理器包的机器,因此数字是正确的。然而,i7会导致这个代码出现问题。当我发布那个解决方案时,i7还不是很多。那时它似乎运作正常。但是随着i7的推出,英特尔使得他们自己的CPID指令几乎不可能用于此目的。 - jcoffland
1
就此而言,我有一颗X5680 CPU,它有六个核心(这是设备管理器中报告的),但是这段代码看到了16个核心和32个逻辑CPU。 - Alastair Maw
4
代码仍然无法正常运行。它在 i5-3317U CPU 上报告了 16 个逻辑核心。 - xis
3
这段代码曾经能够正常工作,但即使对于2010年生产的i3 5x0处理器来说也是错误的,因为在那个时候,英特尔决定在APIC id空间中添加间隔。有关详细信息,请参见我在另一个主题中的回答 - Fizz
显示剩余10条评论

26
注意,这并不能提供预期中的物理内核数量,而是逻辑核心数。

如果您可以使用C++11(感谢alfC在下面的评论):

#include <iostream>
#include <thread>

int main() {
    std::cout << std::thread::hardware_concurrency() << std::endl;
    return 0;
}

否则,也许 Boost 库对您来说是一个选项。与上面相同的代码,但是不同的头文件包含。只需包含<boost/thread.hpp>而不是<thread>


7
这是一个非常简单的解决方案,但它不能区分硬件线程(也称超线程)和物理CPU或核心,而我认为这正是这个问题的关键所在。 - jcoffland
1
是的,你说得对,我错过了这个细节,那么我应该删除我的帖子吗? - math
11
请不要删除您的帖子,这些信息非常有帮助。谢谢,它对我很有帮助! - Austin Salgat

18

这里描述的是仅适用于Windows的解决方案:

GetLogicalProcessorInformation

对于Linux系统,可以通过查看/proc/cpuinfo文件来实现。由于我现在没有运行Linux系统,因此无法提供更多细节。您可以计算物理/逻辑处理器实例数量。如果逻辑计数是物理计数的两倍,则表示启用了超线程技术(仅针对x86架构)。


1
我已经点赞了,因为它有效。为了记录(以防链接在某些时候失效),此答案中的msdn链接使用GetLogicalProcessorInformation,在大多数最近的Windows版本上运行良好。(源代码说:“Windows Server 2003、Windows XP Professional x64 Edition和Windows XP with SP3:此示例报告物理处理器的数量而不是活动处理器核心的数量。”)这个msdn链接不应该与__cpuid的链接混淆,后者不幸的是有一个无法工作的示例(在大多数2010年后的英特尔CPU上)。 - Fizz

15

目前使用CPUID得到的最高票答案似乎已经过时。它报告了错误的逻辑和物理处理器数量。这个回答确认了这一点:cpuid-on-intel-i7-processors

具体来说,对于Intel处理器,使用CPUID.1.EBX [23:16]获取逻辑处理器或使用CPUID.4.EAX [31:26]+1获取物理处理器并不能在我测试的任何Intel处理器上得出正确的结果。

针对Intel处理器应使用CPUID.Bh Intel_thread/Fcore and cache topology。 解决方案似乎并不是轻松的。 针对AMD需要不同的解决方案。

这是由Intel提供的源代码,可以报告物理和逻辑核心的正确数量以及插座的正确数量:https://software.intel.com/en-us/articles/intel-64-architecture-processor-topology-enumeration/。 我在一个80个逻辑核心、40个物理核心、4个插座的Intel系统上测试过。

这是针对AMD的源代码:http://developer.amd.com/resources/documentation-articles/articles-whitepapers/processor-and-core-enumeration-using-cpuid/。 它在我的单插座Intel系统上给出了正确的结果,但在我的四插座系统上没有。我没有AMD系统进行测试。

我还没有深入分析源代码以找到使用CPUID的简单解决方案(如果存在的话)。似乎最好的解决方案是使用库或操作系统调用。

编辑:

这是使用CPUID leaf 11(Bh)获取Intel处理器解决方案。要做到这一点,需要循环遍历逻辑处理器,并从CPUID获取每个逻辑处理器的x2APIC ID,然后计算最低有效位为零的x2APIC ID的数量。 对于没有超线程技术的系统,x2APIC ID将始终是偶数。 对于具有超线程技术的系统,每个x2APIC ID都将具有偶数和奇数版本。

// input:  eax = functionnumber, ecx = 0
// output: eax = output[0], ebx = output[1], ecx = output[2], edx = output[3]
//static inline void cpuid (int output[4], int functionnumber)  

int getNumCores(void) {
    //Assuming an Intel processor with CPUID leaf 11
    int cores = 0;
    #pragma omp parallel reduction(+:cores)
    {
        int regs[4];
        cpuid(regs,11);
        if(!(regs[3]&1)) cores++; 
    }
    return cores;
}

为使此功能正常工作,线程必须被绑定。默认情况下,OpenMP不会绑定线程。设置export OMP_PROC_BIND=true将绑定它们,或者可以像thread-affinity-with-windows-msvc-and-openmp中所示的代码一样进行绑定。

我在我的4核/8 HT系统上进行了测试,在BIOS中启用和禁用超线程时,它返回4。我还在一个4插槽系统上进行了测试,每个插槽都有10个内核/20个HT,它返回了40个内核。

AMD处理器或没有CPUID叶子11的旧Intel处理器必须采取不同的方法。


我给你点赞是因为这个回答比其他的好,但是你并没有技术性地回答问题。他只想知道HT是否启用。计算活动的非HT核心并不能做到这一点。你需要改变你的代码来返回一个是/否的答案,即if(regs[3]&1) ht_cores++,在缩减后 ht_enabled = (ht_cores > 0) - Fizz
此外,AMD代码在当前的英特尔处理器上不能正常工作,基本上是因为旧的英特尔代码枚举也不能正常工作:它假设APIC ID空间中没有间隙。但是,由于2009年末左右开始的英特尔处理器存在这种假设是不正确的。 - Fizz
谢谢你的回答,这非常有用。我有一个相关的问题:如果在BIOS中禁用HT,则CPUID Leaf 11在将ECX设置为0的情况下报告什么级别类型(例如ECX输出值的位8:15)?它是“SMT”还是“core”? - void-pointer
@user3588161,OP要求找到物理核心数或者是否启用超线程。我回答了使用叶子11的英特尔处理器的第一部分。我认为这才是OP真正想知道的。 - Z boson

10
由于每个核心似乎不必要地增加了更多的线程,并且这些线程在每个核心上分布不均,因此这个观点已经不再有效。
从上述观点中汲取了一些想法和概念,我提出了这个解决方案。请批评指正。
//EDIT INCLUDES

#ifdef _WIN32
    #include <windows.h>
#elif MACOS
    #include <sys/param.h>
    #include <sys/sysctl.h>
#else
    #include <unistd.h>
#endif

对于几乎所有操作系统来说,标准的“获取核心数”功能返回的是逻辑核心数。但是要想获取物理核心数,我们必须首先检测CPU是否支持超线程技术。
uint32_t registers[4];
unsigned logicalcpucount;
unsigned physicalcpucount;
#ifdef _WIN32
SYSTEM_INFO systeminfo;
GetSystemInfo( &systeminfo );

logicalcpucount = systeminfo.dwNumberOfProcessors;

#else
logicalcpucount = sysconf( _SC_NPROCESSORS_ONLN );
#endif

我们现在有了逻辑核心数量,为了获得预期的结果,我们首先必须检查是否正在使用超线程或者它是否可用。
__asm__ __volatile__ ("cpuid " :
                      "=a" (registers[0]),
                      "=b" (registers[1]),
                      "=c" (registers[2]),
                      "=d" (registers[3])
                      : "a" (1), "c" (0));

unsigned CPUFeatureSet = registers[3];
bool hyperthreading = CPUFeatureSet & (1 << 28);

因为根据我所了解,英特尔的CPU并没有只对一个核心进行超线程的功能。这使得我们能够以一种非常简单的方式来判断。如果超线程可用,逻辑处理器数量将是物理处理器数量的两倍。否则,操作系统将为每个核心检测到一个逻辑处理器。这意味着逻辑核心数和物理核心数将是相同的。
if (hyperthreading){
    physicalcpucount = logicalcpucount / 2;
} else {
    physicalcpucount = logicalcpucount;
}

fprintf (stdout, "LOGICAL: %i\n", logicalcpucount);
fprintf (stdout, "PHYSICAL: %i\n", physicalcpucount);

我可以做到,谢谢你的提示,需要一些注释或更多吗? - Pnelego
1
我做了一些更改,解释得更好了吗? - Pnelego
是的,这正是我所想的。 - Nathan Tuggy
2
现在有了英特尔的性能核心和超线程核心,我们不能再将总数除以2了吗? - undefined
@disservin 不幸的是,你是正确的。而且,AMD现在似乎也在做同样的事情,所以谁也说不准。 - undefined

8

继续数学的答案,从boost 1.56开始,存在physical_concurrency属性,它正是你想要的。

根据文档 - http://www.boost.org/doc/libs/1_56_0/doc/html/thread/thread_management.html#thread.thread_management.thread.physical_concurrency

当前系统上可用的物理内核数量。与hardware_concurrency()不同,它不返回虚拟内核数,而是仅计算物理内核数。

所以一个例子就是

    #include <iostream>
    #include <boost/thread.hpp>

    int main()
    {
        std::cout << boost::thread::physical_concurrency();
        return 0;
    }

6

我知道这是一个老帖子,但没有人提到hwloc。hwloc库可用于大多数Linux发行版,并且也可以在Windows上编译。下面的代码将返回物理处理器的数量。在i7 CPU的情况下为4。

#include <hwloc.h>

int nPhysicalProcessorCount = 0;

hwloc_topology_t sTopology;

if (hwloc_topology_init(&sTopology) == 0 &&
    hwloc_topology_load(sTopology) == 0)
{
    nPhysicalProcessorCount =
        hwloc_get_nbobjs_by_type(sTopology, HWLOC_OBJ_CORE);

    hwloc_topology_destroy(sTopology);
}

if (nPhysicalProcessorCount < 1)
{
#ifdef _OPENMP
    nPhysicalProcessorCount = omp_get_num_procs();
#else
    nPhysicalProcessorCount = 1;
#endif
}

请描述一下你的代码为什么能够解决 OP 的问题。 - bcesars
我添加了更多信息。这正是OP所寻找的。所有其他建议都不是跨平台的,或者只适用于某些特定的硬件。 - pneveu

5
测试一个Intel CPU是否有超线程是不够的,还需要测试超线程是启用还是禁用。目前没有正式的方法来检查这一点。一个Intel工程师想出了一个小技巧来检查超线程是否启用:使用CPUID[0xa].eax[15:8]检查可编程性能计数器的数量,并假设如果值为8,则HT已禁用;如果值为4,则HT已启用(https://software.intel.com/en-us/forums/intel-isa-extensions/topic/831551)。
对于AMD芯片,没有问题:CPUID报告每个核心1或2个线程,具体取决于是否禁用了同时多线程。
您还需要将CPUID报告的线程计数与操作系统报告的线程计数进行比较,以查看是否存在多个CPU芯片。
我已经编写了一个实现所有这些功能的函数。它报告物理处理器和逻辑处理器的数量。我已在Windows和Linux上测试了Intel和AMD处理器。它也应该在Mac上工作。我已将此代码发布在https://github.com/vectorclass/add-on/tree/master/physical_processors

2

在OS X上,您可以通过sysctl(3)(C API或同名命令行实用程序)读取这些值。手册页面应该会提供使用信息。以下键可能会引起您的兴趣:

$ sysctl hw
hw.ncpu: 24
hw.activecpu: 24
hw.physicalcpu: 12  <-- number of cores
hw.physicalcpu_max: 12
hw.logicalcpu: 24   <-- number of cores including hyper-threaded cores
hw.logicalcpu_max: 24
hw.packages: 2      <-- number of CPU packages
hw.ncpu = 24
hw.availcpu = 24

1
在Windows系统中,有GetLogicalProcessorInformationGetLogicalProcessorInformationEx两个函数可用于Windows XP SP3或更早版本以及Windows 7+。它们的区别在于,GetLogicalProcessorInformation不支持拥有超过64个逻辑核心的设置,这对服务器设置可能很重要,但如果你使用XP系统,你可以退而求其次使用GetLogicalProcessorInformation。以下是GetLogicalProcessorInformationEx的示例用法(source):
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX buffer = NULL;
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX ptr = NULL;
BOOL rc;
DWORD length = 0;
DWORD offset = 0;
DWORD ncpus = 0;
DWORD prev_processor_info_size = 0;
for (;;) {
    rc = psutil_GetLogicalProcessorInformationEx(
            RelationAll, buffer, &length);
    if (rc == FALSE) {
        if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
            if (buffer) {
                free(buffer);
            }
            buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)malloc(length);
            if (NULL == buffer) {
                return NULL;
            }
        }
        else {
            goto return_none;
        }
    }
    else {
        break;
    }
}
ptr = buffer;
while (offset < length) {
    // Advance ptr by the size of the previous
    // SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX struct.
    ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)\
        (((char*)ptr) + prev_processor_info_size);

    if (ptr->Relationship == RelationProcessorCore) {
        ncpus += 1;
    }

    // When offset == length, we've reached the last processor
    // info struct in the buffer.
    offset += ptr->Size;
    prev_processor_info_size = ptr->Size;
}

free(buffer);
if (ncpus != 0) {
    return ncpus;
}
else {
    return NULL;
}

return_none:
if (buffer != NULL)
    free(buffer);
return NULL;

在Linux上,解析/proc/cpuinfo可能会有帮助。

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