以编程方式检测多个物理处理器/核心,或者在Windows,Mac和Linux上是否处于活动状态

时间:2010-05-25 02:57:08

标签: c++ windows macos assembly hyperthreading

我有一个在Windows,Mac和一些Linux风格上运行的多线程c ++应用程序。

总而言之:为了使其以最高效率运行,我必须能够为每个物理处理器/核心实例化一个线程。创建比物理处理器/内核更多的线程会大大降低程序的性能。我已经可以在所有这三个平台上正确检测逻辑处理器/核心的数量。为了能够正确检测物理处理器/内核的数量,我必须检测是否支持超级交叉和活动。

因此,我的问题是,是否有办法检测是否支持超线程并启用?如果是这样,究竟如何。

14 个答案:

答案 0 :(得分:25)

编辑:由于英特尔一直处于困境,这已不再是100%正确。

我理解这个问题的方法是,您正在询问如何检测CPU核心数与CPU线程数,这与检测系统中逻辑和物理核心数量不同。 CPU核心通常不被操作系统视为物理核心,除非它们有自己的封装或裸片。因此,操作系统将报告Core 2 Duo具有1个物理CPU和2个逻辑CPU,并且具有超线程的Intel P4将以完全相同的方式报告,即使2个超线程与2个CPU核心非常相同不同的表现明智。

我一直在努力解决这个问题,直到我将下面的解决方案拼凑在一起,我认为这对AMD和Intel处理器都有效。据我所知,我可能错了,AMD还没有CPU线程,但他们提供了一种检测它们的方法,我认为这些方法可以用于未来可能具有CPU线程的AMD处理器。

简而言之,这里是使用CPUID指令的步骤:

  1. 使用CPUID功能0
  2. 检测CPU供应商
  3. 检查来自CPUID功能1的CPU功能EDX中的HTT位28
  4. 从CPUID函数1
  5. 获取EBX [23:16]的逻辑核心数
  6. 获取实际的非线程CPU核心数
    1. 如果供应商=='GenuineIntel'这是来自CPUID功能4的1加EAX [31:26]
    2. 如果供应商=='AuthenticAMD',这是1加ECX [7:0]来自CPUID函数0x80000008
  7. 听起来很难,但这是一个希望平台独立的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 CPU 3.00GHz:

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

答案 1 :(得分:17)

请注意,并未按预期提供物理核心数,而是提供逻辑核心。

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

#include <iostream>
#include <thread>

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

否则,Boost库可能是您的选择。相同的代码但不同的包括如上。包括<boost/thread.hpp>而不是<thread>

答案 2 :(得分:14)

仅限Windows解决方案:

GetLogicalProcessorInformation

用于linux,/ proc / cpuinfo文件。我没有运行linux 现在所以不能给你更多细节。你可以数数 物理/逻辑处理器实例。如果逻辑计数 是物理的两倍,然后你启用了HT (仅适用于x86)。

答案 3 :(得分:11)

使用CPUID的当前最高投票答案似乎已过时。它报告错误数量的逻辑和物理处理器。这似乎是从这个答案cpuid-on-intel-i7-processors确认的。

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

对于Intel CPUID.Bh应该使用Intel_thread/Fcore and cache topology。解决方案似乎并不简单。对于AMD,需要一个不同的解决方案。

以下是英特尔的源代码,它报告了正确数量的物理和逻辑内核以及正确的套接字数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叶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的旧版英特尔处理器必须做一些不同的事情。

答案 4 :(得分:7)

从数学的答案开始,从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;
    }

答案 5 :(得分: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
}

答案 6 :(得分:6)

通过从上述一些想法中收集想法和概念,我提出了这个解决方案。请批评。

//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);

答案 7 :(得分: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

答案 8 :(得分:1)

仅测试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

答案 9 :(得分:0)

我不知道所有三个都以相同的方式公开信息,但如果您可以安全地假设NT内核将根据POSIX标准(据称NT支持)报告设备信息,那么您可以按照这个标准工作。

但是,设备管理的不同之处通常被认为是跨平台开发的绊脚石之一。我最多将这个作为三个逻辑线实现,我不会尝试编写一段代码来均匀地处理所有平台。

好的,这就是假设C ++。对于ASM,我认为你只能在x86或amd64 CPU上运行?你仍然需要两个分支路径,每个架构一个,你需要测试Intel与AMD(IIRC)分开,但总的来说你只需要检查CPUID。这是你想要找到的吗? Intel / AMD系列CPU上ASM的CPUID?

答案 10 :(得分:0)

在Python中这很容易做到:

$ python -c "import psutil; psutil.cpu_count(logical=False)"
4

也许您可以查看psutil源代码以查看发生了什么?

答案 11 :(得分:0)

在Windows上,分别有GetLogicalProcessorInformationGetLogicalProcessorInformationEx可用于Windows XP SP3或更早版本和Windows 7+。区别在于,GetLogicalProcessorInformation不支持具有64个以上逻辑核心的设置,这对于服务器设置而言可能很重要,但是如果您使用的是XP,则始终可以退回到GetLogicalProcessorInformationGetLogicalProcessorInformationExsource)的用法示例:

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可能会有所帮助。

答案 12 :(得分:0)

您可以使用libcpuid库(也在GitHub-libcpuid上)。

从其documentation page中可以看出:

#include <stdio.h>
#include <libcpuid.h>

int main(void)
{
    if (!cpuid_present()) {                                                // check for CPUID presence
        printf("Sorry, your CPU doesn't support CPUID!\n");
        return -1;
    }

if (cpuid_get_raw_data(&raw) < 0) {                                    // obtain the raw CPUID data
        printf("Sorry, cannot get the CPUID raw data.\n");
        printf("Error: %s\n", cpuid_error());                          // cpuid_error() gives the last error description
        return -2;
}

if (cpu_identify(&raw, &data) < 0) {                                   // identify the CPU, using the given raw data.
        printf("Sorrry, CPU identification failed.\n");
        printf("Error: %s\n", cpuid_error());
        return -3;
}

printf("Found: %s CPU\n", data.vendor_str);                            // print out the vendor string (e.g. `GenuineIntel')
    printf("Processor model is `%s'\n", data.cpu_codename);                // print out the CPU code name (e.g. `Pentium 4 (Northwood)')
    printf("The full brand string is `%s'\n", data.brand_str);             // print out the CPU brand string
    printf("The processor has %dK L1 cache and %dK L2 cache\n",
        data.l1_data_cache, data.l2_cache);                            // print out cache size information
    printf("The processor has %d cores and %d logical processors\n",
        data.num_cores, data.num_logical_cpus);                        // print out CPU cores information

}

可以看出,data.num_cores拥有CPU的物理内核数。

答案 13 :(得分:-1)

OpenMP应该可以解决这个问题:

// test.cpp
#include <omp.h>
#include <iostream>

using namespace std;

int main(int argc, char** argv) {
  int nThreads = omp_get_max_threads();
  cout << "Can run as many as: " << nThreads << " threads." << endl;
}

大多数编译器都支持OpenMP。如果您使用的是基于gcc的编译器(* nix,MacOS),则需要使用:

进行编译
$ g++ -fopenmp -o test.o test.cpp

(您可能还需要告诉编译器使用stdc ++库):

$ g++ -fopenmp -o test.o -lstdc++ test.cpp

据我所知,OpenMP旨在解决这类问题。