Avatar ·

Как программно определить количество ядер процессора на C++ (кроссплатформенное решение)?

Всем привет! Пишу многопоточное приложение на C++ для обработки изображений. Хочу, чтобы программа сама определяла оптимальное количество потоков в зависимости от железа пользователя. Проблема в том, что нужно работать и на Windows, и на Linux. Пытался использовать макросы и вызовы системных API, но получается какой-то зоопарк: ```cpp #ifdef _WIN32 SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); int cores = sysinfo.dwNumberOfProcessors; #elif __linux__ int cores = sysconf(_SC_NPROCESSORS_ONLN); #else int cores = 1; // fallback #endif ``` Это работает, но код выглядит ужасно. Плюс, я читал, что на некоторых процессорах с Hyper-Threading `dwNumberOfProcessors` возвращает логические ядра, а не физические. А мне нужно именно количество физических ядер, чтобы не создавать лишних потоков и не перегружать кэш. Есть ли более элегантный способ сделать это в современном C++ (C++11/14/17)? Может, есть какая-то стандартная функция или библиотека, которая абстрагируется от платформы? И как гарантированно получить количество физических ядер, а не логических потоков? Подскажите, пожалуйста, как правильно решить эту задачу. <<>>

Решение на C++17 с использованием std::thread::hardware_concurrency

В современном C++ (начиная с C++11) есть стандартный способ получить количество аппаратных потоков — функция std::thread::hardware_concurrency(). Она возвращает число аппаратных потоков (логических ядер), которые могут выполняться одновременно. Это кроссплатформенное решение, которое работает на Windows, Linux и macOS.

#include <iostream>
#include <thread>

int main() {
    unsigned int logical_cores = std::thread::hardware_concurrency();
    std::cout << "Логических ядер: " << logical_cores << std::endl;
    return 0;
}

Важное замечание: Если функция не может определить количество ядер, она возвращает 0. Всегда проверяйте это значение перед использованием:

unsigned int cores = std::thread::hardware_concurrency();
if (cores == 0) {
    cores = 1; // или другое значение по умолчанию
}

Как получить количество физических ядер?

Стандартный C++ не предоставляет способа различить физические и логические ядра. Это особенность аппаратуры и операционной системы. Однако вы можете использовать платформозависимые API в сочетании с условной компиляцией, но сделать это "чище", чем в вашем примере:

#include <iostream>

#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

int getPhysicalCores() {
    SYSTEM_INFO sysinfo;
    GetSystemInfo(&sysinfo);
    return sysinfo.dwNumberOfProcessors;
}
#elif defined(__linux__)
#include <unistd.h>
#include <fstream>
#include <string>

int getPhysicalCores() {
    // Читаем /proc/cpuinfo, считаем уникальные "core id"
    std::ifstream cpuinfo("/proc/cpuinfo");
    std::string line;
    int physical_cores = 0;
    int last_core_id = -1;
    while (std::getline(cpuinfo, line)) {
        if (line.find("core id") != std::string::npos) {
            int core_id = std::stoi(line.substr(line.find(':') + 1));
            if (core_id != last_core_id) {
                physical_cores++;
                last_core_id = core_id;
            }
        }
    }
    return physical_cores > 0 ? physical_cores : 1;
}
#elif defined(__APPLE__)
#include <sys/sysctl.h>

int getPhysicalCores() {
    int count;
    size_t size = sizeof(count);
    sysctlbyname("hw.physicalcpu", &count, &size, nullptr, 0);
    return count > 0 ? count : 1;
}
#else
int getPhysicalCores() {
    return 1; // fallback
}
#endif

int main() {
    std::cout << "Физических ядер: " << getPhysicalCores() << std::endl;
    return 0;
}

Рекомендации по выбору количества потоков

Для большинства задач оптимальное количество потоков равно std::thread::hardware_concurrency() или hardware_concurrency() - 1 (чтобы оставить один поток для ОС). Если вы обрабатываете данные и боитесь перегрузки кэша, попробуйте использовать hardware_concurrency() / 2 как начальное значение и профилируйте производительность.

Помните: не всегда больше потоков = быстрее. Из-за накладных расходов на переключение контекста и синхронизацию, иногда 4 потока на 8-ядерном процессоре работают быстрее, чем 8.

<<>>

Альтернативный подход: использование библиотеки Boost

Если вы не хотите писать платформозависимый код, но нужна более точная информация о процессоре, чем дает std::thread::hardware_concurrency, рассмотрите библиотеку Boost. Она предоставляет кроссплатформенный интерфейс для получения информации об оборудовании.

#include <boost/compute/system.hpp>
#include <iostream>

int main() {
    // Количество логических ядер
    unsigned int logical = boost::thread::hardware_concurrency();
    
    // Получить количество физических ядер можно через Boost.Compute
    auto device = boost::compute::system::default_device();
    unsigned int physical = device.compute_units();
    
    std::cout << "Логических ядер: " << logical << std::endl;
    std::cout << "Физических ядер (compute units): " << physical << std::endl;
    
    return 0;
}

Плюсы:

  • Единый API для всех платформ
  • Доступ к дополнительной информации (кэш, частота, архитектура)
  • Хорошо протестирован и поддерживается

Минусы:

  • Требует установки Boost (увеличивает размер проекта)
  • Для простых задач может быть избыточным

Если ваш проект уже использует Boost, это отличный вариант. В противном случае, для большинства задач достаточно std::thread::hardware_concurrency() — это стандартный, современный и кроссплатформенный подход.