Программно определять количество физических процессоров / ядер или, если гиперпоточность активна в Windows, Mac и Linux

У меня есть многопоточное приложение C++, которое работает на Windows, Mac и нескольких версиях Linux.

Короче говоря: для того, чтобы он работал с максимальной эффективностью, я должен иметь возможность создавать отдельный поток на физический процессор / ядро. Создание большего количества потоков, чем существует физических процессоров / ядер, значительно снижает производительность моей программы. Я уже могу правильно определить количество логических процессоров / ядер на всех трех этих платформах. Чтобы иметь возможность правильно определять количество физических процессоров / ядер, я должен определить, поддерживается ли гипертрединг И активен ли он.

Поэтому мой вопрос заключается в том, существует ли способ определить, поддерживается ли ГИПЕРТРЕПИДИРОВАНИЕ И РАЗРЕШЕНО? Если да, то как именно.

11 ответов

РЕДАКТИРОВАТЬ: Это больше не на 100% правильно из-за продолжающегося недоумения Intel.

Насколько я понимаю, вопрос заключается в том, что вы спрашиваете, как определить количество ядер ЦП и потоков ЦП, которое отличается от определения количества логических и физических ядер в системе. Ядра процессора часто не считаются физическими ядрами в ОС, если они не имеют своего собственного пакета или умирают. Таким образом, ОС сообщит, что Core 2 Duo, например, имеет 1 физический и 2 логических ЦП, а Intel P4 с гиперпотоками будет отображаться точно так же, даже если 2 гиперпотока против 2 ядер ЦП очень другое дело производительность мудрая.

Я боролся с этим, пока не собрал воедино приведенное ниже решение, которое, как мне кажется, работает как для процессоров AMD, так и для процессоров Intel. Насколько я знаю, и я могу ошибаться, у AMD пока нет потоков ЦП, но они предоставили способ их обнаружения, который, как я полагаю, будет работать на будущих процессорах AMD, которые могут иметь потоки ЦП.

Вкратце вот шаги с использованием инструкции CPUID:

  1. Определить поставщика ЦП с помощью функции CPUID 0
  2. Проверьте HTT бит 28 в функциях процессора EDX из функции CPUID 1
  3. Получить счетчик логических ядер из EBX[23:16] из функции CPUID 1
  4. Получите фактическое число непроцессорных ядер процессора
    1. Если vendor == "GenuineIntel", это 1 плюс EAX[31:26] из функции CPUID 4
    2. Если vendor == 'AuthenticAMD', это 1 плюс ECX[7:0] от функции CPUID 0x80000008

Звучит сложно, но есть, надеюсь, независимая от платформы программа на 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® Core ™ 2 Duo T7500 с частотой 2,20 ГГц:

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

Четырехъядерный процессор Intel(R) Core ™ 2 Q8400 с частотой 2,66 ГГц:

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

Процессор Intel® R Xeon®® E5520 @ 2,27 ГГц (с пакетами физических процессоров x2):

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

Процессор Intel(R) Pentium(4) 3,00 ГГц:

 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-процессоров.

В частности, использование CPUID.1.EBX[23:16] для получения логических процессоров или CPUID.4.EAX[31:26]+1 для получения физических с процессорами Intel не дает правильного результата на любом процессоре Intel I иметь.

Для Intel CPUID.Bh следует использовать Intel_thread/Fcore и топологию кеша. Решение не кажется тривиальным. Для AMD необходимо другое решение.

Вот исходный код Intel, который сообщает правильное количество физических и логических ядер, а также правильное количество сокетов https://software.intel.com/en-us/articles/intel-64-architecture-processor-topology-enumeration/ Я проверил это на 80 логическом ядре, 40 физических ядрах, системе Intel с 4 сокетами.

Вот исходный код для AMD http://developer.amd.com/resources/documentation-articles/articles-whitepapers/processor-and-core-enumeration-using-cpuid/. Это дало правильный результат на моей системе Intel с одним сокетом, но не на моей системе с четырьмя сокетами. У меня нет системы AMD для тестирования.

Я еще не проанализировал исходный код, чтобы найти простой ответ (если таковой существует) с CPUID. Похоже, что если решение может измениться (как, кажется, имеет), то лучшим решением будет использование вызова библиотеки или ОС.

Редактировать:

Вот решение для процессоров Intel с CPUID, лист 11 (Bh). Способ сделать это - перебрать логические процессоры и получить идентификатор x2APIC для каждого логического процессора из CPUID и подсчитать количество идентификаторов x2APIC, в которых младший значащий бит равен нулю. Для систем без гиперпоточности идентификатор x2APIC всегда будет четным. Для систем с гиперпоточностью каждый идентификатор x2APIC будет иметь четную и нечетную версию.

// 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 свяжет их или они могут быть связаны в коде, как показано в /questions/20525108/shodstvo-potokov-s-windows-msvc-i-openmp.

Я проверил это на моей 4-ядерной /8 HT-системе, и она вернула 4 с отключенной гиперпоточностью в BIOS. Я также проверил систему с 4 сокетами, в каждом из которых было 10 ядер / 20 HT, и было возвращено 40 ядер.

Процессоры AMD или более старые процессоры Intel без CPUID leaf 11 должны делать что-то другое.

Исходя из сбора идей и концепций из некоторых вышеупомянутых идей, я придумал это решение. Пожалуйста, критикуйте.

//EDIT INCLUDES

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

Почти для каждой ОС стандартная функция "Get core count" возвращает количество логических ядер. Но для того, чтобы получить количество физических ядер, мы должны сначала определить, есть ли у процессора гиперпоточность или нет.

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

Потому что нет процессора Intel с гиперпоточностью, который будет гиперпоточен только на одно ядро ​​(по крайней мере, из того, что я читал). Это позволяет нам найти это действительно безболезненным способом. Если доступна гиперпоточность, логические процессоры будут в два раза больше физических процессоров. В противном случае операционная система обнаружит логический процессор для каждого ядра. Значение логического и физического количества ядер будет одинаковым.

if (hyperthreading){
    physicalcpucount = logicalcpucount / 2;
} else {
    physicalcpucount = logicalcpucount;
}

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

Следуя математическому ответу, начиная с буста 1.56, существует атрибут Physical_concurrency, который делает именно то, что вы хотите.

Из документации - http://www.boost.org/doc/libs/1_56_0/doc/html/thread/thread_management.html

Количество физических ядер, доступных в текущей системе. В отличие от hardware_concurrency() он не возвращает количество виртуальных ядер, но учитывает только физические ядра.

Так что пример будет

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

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

Я знаю, что это старая тема, но никто не упомянул hwloc. Библиотека hwloc доступна в большинстве дистрибутивов Linux, а также может быть скомпилирована в Windows. Следующий код вернет количество физических процессоров. 4 в случае с процессором i7.

#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 гиперпоточность, вам также необходимо проверить, включена или отключена гиперпоточность. Нет документированного способа проверить это. Парень из 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 со счетчиком потоков, сообщенным операционной системой, чтобы увидеть, есть ли несколько микросхем ЦП.

Я сделал функцию, которая реализует все это. Он сообщает как количество физических процессоров, так и количество логических процессоров. Я тестировал его на процессорах Intel и AMD в Windows и Linux. Он также должен работать на Mac. Я опубликовал этот код на https://github.com/vectorclass/add-on/tree/master/physical_processors

На OS X вы можете прочитать эти значения из sysctl(3) (C API или утилита командной строки с тем же именем). Страница man должна дать вам информацию об использовании. Следующие ключи могут представлять интерес:

$ 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(источник):

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 может помочь.

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 был разработан для решения подобных проблем.

Это очень легко сделать в Python:

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

Может быть, вы могли бы посмотреть на psutil Исходный код, чтобы увидеть, что происходит?

Я не знаю, что все три предоставляют информацию одинаково, но если вы можете с уверенностью предположить, что ядро ​​NT будет сообщать информацию об устройстве в соответствии со стандартом POSIX (который NT предположительно поддерживает), то вы можете отработать это. стандарт.

Тем не менее, различные устройства управления часто упоминаются как один из камней преткновения для кросс-платформенной разработки. В лучшем случае я бы реализовал это как три нити логики, я бы не пытался написать один кусок кода, который бы равномерно обрабатывал все платформы.

Хорошо, все, что предполагает C++. Я полагаю, что для ASM вы будете работать только на процессорах x86 или amd64? Вам все еще понадобятся два пути ветвления, по одному для каждой архитектуры, и вам нужно будет тестировать Intel отдельно от AMD (IIRC), но в целом вы просто проверяете CPUID. Это то, что вы пытаетесь найти? CPUID от ASM на процессорах семейства Intel/AMD?

Вы можете использовать библиотеку 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, содержит количество физических ядер ЦП.

Другие вопросы по тегам