#c #multithreading #openmp #affinity
Вопрос:
Используя x64 msvc v19.29 на AMD Ryzen 9 5900X (12 ядер, отключен эквивалент гиперпотока AMD), программа распараллеливается только с использованием omp_set_num_threads( 10 );
и, #pragma omp parallel for schedule( dynamic, 1 )
похоже, привязывает один главный и девять рабочих потоков к разным ядрам. Наблюдая за диспетчером задач Windows, 10 ядер используются на 100%, в то время как два ядра более или менее простаивают. Простаивающие и неактивированные ядра меняются при каждом перезапуске программы, но наблюдаемое поведение остается неизменным. Проверка сходства потоков для каждого потока в статическом цикле OpenMP с использованием возвращаемого значения SetThreadAffinitMask( . )
показывает, что всем потокам разрешено работать на всех 12 ядрах.
Напротив, при использовании пула потоков на основе C 17 (здесь главный поток не выполняет никакой работы, а все 10 рабочих потоков) можно наблюдать серьезный пинг-понг потоков, и нет квази-незанятых ядер. Излишне говорить, что производительность варианта OpenMP значительно выше.
Мой вопрос в том, какой механизм используется OpenMP для того, чтобы вызвать наблюдаемое поведение, если он не привязывает потоки к разным ядрам? Как можно воспроизвести поведение с помощью API Windows?
В качестве примечания я не решаюсь использовать OpenMP, поскольку он не гарантирует работу с конструкциями C 11 или выше (насколько мне известно и игнорируя экспериментальные настройки, Visual Studio поддерживает только OpenMP 2.0).
Правка 1: Ответ на вопрос Дж. Ричарда
auto getMask =
[]( HANDLE handle )
{
auto mask = DWORD_PTR{ 1 };
mask = SetThreadAffinityMask( handle, mask );
SetThreadAffinityMask( handle, mask );
return mask;
};
std::vector< DWORD_PTR > masks( 10 );
#pragma omp parallel for schedule( static, 1 )
for ( auto i = 0; i < 10; i )
{
masks[ i ] = getMask( GetCurrentThread() );
}
Каждый элемент masks
равен 4095 (десятичный) или 111111111111 (двоичный).
Комментарии:
1. Разница в производительности, которую вы измерили, удивительна. Похоже, что алгоритм (записанный в любой пул потоков, который вы используете) не очень хорошо разработан. Все равно будут накладные расходы, если вы будете разбивать пул потоков на каждой небольшой итерации. Так что не делай этого.
2. Можете ли вы уточнить, как вы проверили близость
SetThreadAffinitMask
и точные результаты?