我有一个在 Windows、Mac 和一些 Linux 版本上运行的多线程 c++ 应用程序。
长话短说:为了让它以最高效率运行,我必须能够为每个物理处理器/内核实例化一个线程。创建比物理处理器/内核更多的线程会大大降低我的程序的性能。我已经可以在所有这三个平台上正确检测逻辑处理器/内核的数量。为了能够正确检测物理处理器/内核的数量,我必须检测是否支持超线程并处于活动状态。
因此,我的问题是是否有办法检测是否支持和启用超线程?如果是这样,具体如何。
我有一个在 Windows、Mac 和一些 Linux 版本上运行的多线程 c++ 应用程序。
长话短说:为了让它以最高效率运行,我必须能够为每个物理处理器/内核实例化一个线程。创建比物理处理器/内核更多的线程会大大降低我的程序的性能。我已经可以在所有这三个平台上正确检测逻辑处理器/内核的数量。为了能够正确检测物理处理器/内核的数量,我必须检测是否支持超线程并处于活动状态。
因此,我的问题是是否有办法检测是否支持和启用超线程?如果是这样,具体如何。
编辑:由于英特尔的持续困惑,这不再是 100% 正确的。
我理解这个问题的方式是,您问的是如何检测 CPU 内核数与 CPU 线程数,这与检测系统中的逻辑和物理内核数不同。CPU 内核通常不被操作系统视为物理内核,除非它们有自己的封装或芯片。因此,操作系统将报告 Core 2 Duo,例如,具有 1 个物理 CPU 和 2 个逻辑 CPU,而具有超线程的 Intel P4 将报告完全相同的方式,即使 2 个超线程与 2 个 CPU 内核是一个非常不同的事情表现明智。
我一直在努力解决这个问题,直到我拼凑出下面的解决方案,我相信它适用于 AMD 和 Intel 处理器。据我所知,我可能是错的,AMD 还没有 CPU 线程,但他们提供了一种检测它们的方法,我认为这种方法可以在未来可能有 CPU 线程的 AMD 处理器上工作。
简而言之,这里是使用 CPUID 指令的步骤:
听起来很困难,但这里有一个平台独立的 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(R) Core(TM)2 双核 CPU T7500 @ 2.20GHz:
logical cpus: 2
cpu cores: 2
hyper-threads: false
Intel(R) Core(TM)2 四核 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
请注意,它并没有给出预期的物理核心数,而是逻辑核心数。
如果您可以使用 C++11(感谢 alfC 下面的评论):
#include <iostream>
#include <thread>
int main() {
std::cout << std::thread::hardware_concurrency() << std::endl;
return 0;
}
否则,也许 Boost 库是您的选择。相同的代码但不同的包含如上。包括<boost/thread.hpp>
而不是<thread>
.
此处描述的仅限 Windows 的解决方案:
GetLogicalProcessorInformation
对于 linux,/proc/cpuinfo 文件。我现在没有运行linux,所以不能给你更多细节。您可以计算物理/逻辑处理器实例。如果逻辑计数是物理计数的两倍,那么您启用了 HT(仅适用于 x86)。
当前使用 CPUID 的最高投票答案似乎已过时。它报告错误数量的逻辑和物理处理器。这似乎从这个答案cpuid-on-intel-i7-processors得到证实。
具体来说,使用 CPUID.1.EBX[23:16] 获取逻辑处理器或 CPUID.4.EAX[31:26]+1 使用英特尔处理器获取物理处理器不会在任何英特尔处理器上给出正确的结果 I有。
对于 Intel CPUID.Bh 应使用Intel_thread/Fcore 和缓存拓扑。解决方案似乎并不简单。对于 AMD,需要不同的解决方案。
这是英特尔的源代码,它报告了正确数量的物理和逻辑内核以及正确的套接字数量https://software.intel.com/en-us/articles/intel-64-architecture-processor-topology -枚举/。我在 80 个逻辑核心、40 个物理核心、4 个插槽的 Intel 系统上对此进行了测试。
这是 AMD http://developer.amd.com/resources/documentation-articles/articles-whitepapers/processor-and-core-enumeration-using-cpuid/的源代码。它在我的单路英特尔系统上给出了正确的结果,但在我的四路系统上却没有。我没有要测试的 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 系统上对此进行了测试,它返回 4,在 BIOS 中禁用和不禁用超线程。我还在一个 4 插槽系统上进行了测试,每个插槽有 10 个内核/20 个 HT,它返回 40 个内核。
没有 CPUID 叶 11 的 AMD 处理器或较旧的 Intel 处理器必须做一些不同的事情。
通过从上述一些想法中收集想法和概念,我想出了这个解决方案。请批评。
//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);
从数学的答案开始,从 boost 1.56 开始,存在 physical_concurrency 属性,它完全符合您的要求。
当前系统上可用的物理内核数。与 hardware_concurrency() 相比,它不返回虚拟内核的数量,但它只计算物理内核。
所以一个例子是
#include <iostream>
#include <boost/thread.hpp>
int main()
{
std::cout << boost::thread::physical_concurrency();
return 0;
}
我知道这是一个旧线程,但没有人提到hwloc。hwloc 库在大多数 Linux 发行版上都可用,也可以在 Windows 上编译。以下代码将返回物理处理器的数量。4 在 i7 CPU 的情况下。
#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
}
仅测试 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上发布了此代码
sysctl(3)
在 OS X 上,您可以从(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
在 Windows 上,分别GetLogicalProcessorInformation
适用GetLogicalProcessorInformationEx
于 Windows XP SP3 或更早版本和 Windows 7+。不同之处在于 GetLogicalProcessorInformation 不支持超过 64 个逻辑核心的设置,这对于服务器设置可能很重要,但GetLogicalProcessorInformation
如果您使用的是 XP,则可以随时回退。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
可能会有所帮助。
我不知道这三个都以相同的方式公开信息,但是如果您可以安全地假设 NT 内核将根据 POSIX 标准(据说 NT 支持)报告设备信息,那么您可以解决这个问题标准。
然而,设备管理的差异经常被认为是跨平台开发的绊脚石之一。我充其量将其实现为三股逻辑,我不会尝试编写一段代码来均匀地处理所有平台。
好的,所有这些都假设 C++。对于 ASM,我认为您只能在 x86 或 amd64 CPU 上运行?您仍然需要两个分支路径,每个架构一个,并且您需要将 Intel 与 AMD (IIRC) 分开测试,但总的来说,您只需检查 CPUID。那是你想要找到的吗?Intel/AMD 系列 CPU 上的 ASM 的 CPUID?
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 旨在解决此类问题。
您可以使用该库libcpuid
(也在 GitHub - 上libcpuid
)。
从它的文档页面可以看出:
#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 的物理内核数。
这在 Python 中很容易做到:
$ python -c "import psutil; psutil.cpu_count(logical=False)"
4
也许您可以查看psutil
源代码以了解发生了什么?