Detecta programáticamente el número de procesadores físicos / núcleos o si hyper-threading está activo en Windows, Mac y Linux

Tengo una aplicación c ++ multiproceso que se ejecuta en Windows, Mac y algunos sabores de Linux.

Para resumir: para que funcione con la máxima eficiencia, tengo que ser capaz de crear una sola hebra por procesador / núcleo físico. Crear más hilos que procesadores / núcleos físicos degrada considerablemente el rendimiento de mi progtwig. Ya puedo detectar correctamente la cantidad de procesadores lógicos / núcleos correctamente en las tres plataformas. Para poder detectar correctamente el número de procesadores / núcleos físicos, tendré que detectar si el hiperespacio es compatible Y activo.

Mi pregunta por lo tanto es si hay una manera de detectar si hyperthreading es compatible Y HABILITADO? Si es así, ¿cómo exactamente?

EDITAR: Esto ya no es 100% correcto debido al continuo desconcierto de Intel.

La forma en que entiendo la pregunta es que usted está preguntando cómo detectar la cantidad de núcleos de CPU frente a subprocesos de CPU, que es diferente de la detección del número de núcleos lógicos y físicos en un sistema. El sistema operativo no suele considerar núcleos de CPU a menos que tengan su propio paquete o fallezcan. Entonces, un SO informará que un Core 2 Duo, por ejemplo, tiene 1 CPU física y 2 CPUs lógicas, y un Intel P4 con hiper-hilos se informará exactamente de la misma manera, aunque 2 hiper-hilos vs. 2 núcleos de CPU es una muy diferente cosa rendimiento sabio.

Luché con esto hasta que reconstruí la solución a continuación, que creo que funciona para los procesadores AMD e Intel. Hasta donde yo sé, y podría estar equivocado, AMD aún no cuenta con hilos de CPU, pero han proporcionado una forma de detectarlos que, supongo, funcionarán en futuros procesadores AMD que puedan tener hilos de CPU.

En resumen, aquí están los pasos usando la instrucción CPUID:

  1. Detecta el proveedor de CPU usando la función CPUID 0
  2. Verifique el bit 28 de HTT en las funciones de la CPU EDX desde la función CPUID 1
  3. Obtenga el recuento de núcleos lógicos de EBX [23:16] desde la función CPUID 1
  4. Obtenga un recuento real de CPU sin rosca
    1. Si el proveedor == ‘GenuineIntel’ esto es 1 más EAX [31:26] de la función CPUID 4
    2. Si el proveedor == ‘AuthenticAMD’ esto es 1 más ECX [7: 0] de la función CPUID 0x80000008

Suena difícil, pero he aquí un progtwig de C ++, con suerte independiente de la plataforma, que hace el truco:

#include  #include  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; } 

Todavía no lo he probado en Windows o OSX, pero debería funcionar, ya que la instrucción CPUID es válida en las máquinas i686. Obviamente, esto no funcionará para PowerPC, pero tampoco tienen hipersubprocesos.

Aquí está la salida en algunas máquinas Intel diferentes:

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

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

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

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

CPU Intel (R) Xeon (R) E5520 @ 2.27 GHz (con paquetes de CPU físicos x2):

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

Intel (R) Pentium (R) 4 CPU 3.00GHz:

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

Tenga en cuenta esto, no da la cantidad de núcleos físicos como se pretendía, sino núcleos lógicos.

Si puede usar C ++ 11 (gracias al comentario de alfC debajo):

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

De lo contrario, tal vez la biblioteca Boost sea una opción para ti. El mismo código pero diferente incluye lo anterior. Incluye lugar de .

Solución solo de Windows descrita aquí:

GetLogicalProcessorInformation

para el archivo linux, / proc / cpuinfo. No estoy ejecutando Linux ahora, así que no puedo darle más detalles. Puede contar instancias de procesador físico / lógico. Si el conteo lógico es dos veces más físico, entonces tiene HT habilitado (verdadero solo para x86).

La respuesta más votado actual que usa CPUID parece estar obsoleta. Informa tanto el número incorrecto de procesadores lógicos como físicos. Esto parece confirmarse con esta respuesta cpuid-on-intel-i7-processor .

Específicamente, el uso de CPUID.1.EBX [23:16] para obtener los procesadores lógicos o CPUID.4.EAX [31:26] +1 para obtener los físicos con los procesadores Intel no da el resultado correcto en ningún procesador Intel I tener.

Para Intel CPUID.Bh se debe utilizar la topología Intel_thread / Fcore y caché . La solución no parece ser trivial. Para AMD, se necesita una solución diferente.

Aquí está el código fuente de Intel que informa la cantidad correcta de núcleos físicos y lógicos, así como el número correcto de conectores https://software.intel.com/en-us/articles/intel-64-architecture-processor-topology -enumeración / . Probé esto en un núcleo lógico de 80, 40 núcleos físicos, sistema de 4 sockets Intel.

Aquí está el código fuente de AMD http://developer.amd.com/resources/documentation-articles/articles-whitepapers/processor-and-core-enumeration-using-cpuid/ . Dio el resultado correcto en mi sistema Intel solo socket pero no en mi sistema de cuatro sockets. No tengo un sistema AMD para probar.

No he analizado el código fuente aún para encontrar una respuesta simple (si existe) con CPUID. Parece que si la solución puede cambiar (como parece tener), la mejor solución es usar una biblioteca o una llamada al sistema operativo.

Editar:

Aquí hay una solución para procesadores Intel con CPUID hoja 11 (Bh). La forma de hacerlo es recorrer los procesadores lógicos y obtener el ID de x2APIC para cada procesador lógico desde el CPUID y contar el número de ID de x2APIC donde el bit menos significativo es cero. Para sistemas sin hiper-threading, la identificación de x2APIC siempre será pareja. Para sistemas con hiper-threading, cada ID x2APIC tendrá una versión par e impar.

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

Los hilos deben estar obligados a que esto funcione. OpenMP por defecto no vincula hilos. La configuración de la export OMP_PROC_BIND=true los vinculará o se podrán enlazar en código como se muestra en thread-affinity-with-windows-msvc-and-openmp .

Probé esto en mi sistema 4 core / 8 HT y devolvió 4 con y sin hyper-threading deshabilitado en el BIOS. También probé en un sistema de 4 zócalos con cada zócalo con 10 núcleos / 20 HT y devolvió 40 núcleos.

Los procesadores AMD o los procesadores Intel anteriores sin CPUID hoja 11 tienen que hacer algo diferente.

Desde la recostackción de ideas y conceptos de algunas de las ideas anteriores, he encontrado esta solución. Por favor critica.

 //EDIT INCLUDES #ifdef _WIN32 #include  #elif MACOS #include  #include  #else #include  #endif 

Para casi todos los sistemas operativos, la función estándar “Obtener recuento de núcleos” devuelve el recuento de núcleos lógicos. Pero para obtener el recuento de núcleos físicos, primero debemos detectar si la CPU tiene hipersubprocesamiento o no.

 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 

Ahora tenemos el recuento de núcleos lógico, ahora para obtener los resultados esperados, primero debemos verificar si Hyper Threading está siendo utilizado o si está disponible.

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

Porque no hay una CPU Intel con hiper-enhebrado que solo hiper-hilo un núcleo (al menos no por lo que he leído). Esto nos permite encontrar que esta es una manera realmente indolora. Si hyper threading está disponible, los procesadores lógicos duplicarán exactamente los procesadores físicos. De lo contrario, el sistema operativo detectará un procesador lógico para cada núcleo. Lo que significa que la cuenta del núcleo lógico y físico será idéntica.

 if (hyperthreading){ physicalcpucount = logicalcpucount / 2; } else { physicalcpucount = logicalcpucount; } fprintf (stdout, "LOGICAL: %i\n", logicalcpucount); fprintf (stdout, "PHYSICAL: %i\n", physicalcpucount); 

Para seguir desde la respuesta de las matemáticas, a partir del impulso 1.56 existe el atributo physical_concurrency que hace exactamente lo que usted desea.

De la documentación – http://www.boost.org/doc/libs/1_56_0/doc/html/thread/thread_management.html#thread.thread_management.thread.physical_concurrency

La cantidad de núcleos físicos disponibles en el sistema actual. A diferencia de hardware_concurrency (), no devuelve la cantidad de núcleos virtuales, pero solo cuenta los núcleos físicos.

Entonces un ejemplo sería

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

Sé que este es un hilo viejo, pero nadie mencionó hwloc . La biblioteca hwloc está disponible en la mayoría de las distribuciones de Linux y también se puede comstackr en Windows. El siguiente código devolverá la cantidad de procesadores físicos. 4 en el caso de una CPU i7.

 #include  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 } 

En OS X, puede leer estos valores de sysctl(3) (la API de C o la utilidad de línea de comandos del mismo nombre). La página man debería darle información de uso. Las siguientes claves pueden ser de interés:

 $ 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 

No sé si los tres exponen la información de la misma manera, pero si puede suponer con seguridad que el kernel NT informará la información del dispositivo de acuerdo con el estándar POSIX (que supuestamente NT tiene soporte), entonces podría trabajar en eso estándar.

Sin embargo, la administración diferente de dispositivos se cita a menudo como uno de los obstáculos para el desarrollo de plataformas cruzadas. En el mejor de los casos, implementaría esto como tres líneas de lógica, no trataría de escribir una pieza de código para manejar todas las plataformas de manera uniforme.

Ok, todo eso está asumiendo C ++. Para ASM, supongo que solo se ejecutará en las CPU x86 o AMD64. Todavía necesitará dos rutas de acceso, una para cada architecture, y tendrá que probar Intel por separado de AMD (IIRC), pero en general solo debe verificar el CPUID. ¿Es eso lo que estás tratando de encontrar? ¿El CPUID de ASM en las CPU de la familia Intel / AMD?

Esto es muy fácil de hacer en Python:

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

¿Tal vez podrías mirar el código fuente de psutil para ver qué está pasando?

OpenMP debería hacer el truco:

 // test.cpp #include  #include  using namespace std; int main(int argc, char** argv) { int nThreads = omp_get_max_threads(); cout << "Can run as many as: " << nThreads << " threads." << endl; } 

la mayoría de los comstackdores admiten OpenMP. Si está utilizando un comstackdor basado en gcc (* nix, MacOS), debe comstackr usando:

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

(También es posible que necesite decirle a su comstackdor que use la biblioteca stdc ++):

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

Por lo que sé, OpenMP fue diseñado para resolver este tipo de problemas.