OpenMP: не использовать ядра с гиперпоточностью (половина `num_threads ()` с гиперпоточностью)

В OpenMP (параллельный для) в g ++ 4.7 не очень эффективен? 2,5x при 5x процессоре, Я определил, что производительность моей программы варьируется между 11 и 13 секундами (в основном всегда выше 12 секунд, а иногда даже медленнее, чем 13,4 с), примерно на 500% CPU при использовании по умолчанию #pragma omp parallel forи ускорение OpenMP составляет всего 2,5x при 5x CPU с g++-4.7 -O3 -fopenmpНа 4-х ядерном 8-ниточном Xeon.

Я пытался с помощью schedule(static) num_threads(4)и заметил, что моя программа всегда завершает работу с 11,5 с до 11,7 с (всегда ниже 12 с) при примерно 320% ЦП, например, работает более согласованно и использует меньше ресурсов (даже если лучший прогон на полсекунды медленнее, чем редкий выброс с гиперпоточностью).

Есть ли какой-нибудь простой OpenMP-способ для обнаружения гиперпоточности и уменьшения num_threads() к фактическому количеству ядер процессора?

(Есть похожий вопрос, Низкая производительность из-за гиперпоточности с OpenMP: как привязать потоки к ядрам, но в моем тестировании я обнаружил, что простое сокращение с 8 до 4 потоков каким-то образом уже выполняет эту работу с g / g ++ — 4.7 на Debian 7 wheezy и Xeon E3-1240v3, так что этот вопрос касается лишь сокращения num_threads() на количество ядер.)

4

Решение

Если вы работали под Linux [также предполагая x86 arch], вы можете посмотреть на /proc/cpuinfo, Есть два поля cpu cores а также siblings, Первый — это количество [реальных] ядер, а второй — количество гипер-нитей. (например, в моей системе их 4 и 8 соответственно для моей четырехъядерной гиперпоточной машины).

Поскольку Linux может обнаружить это [и по ссылке в комментарии Зулана], информация также доступна из x86 cpuid инструкция.

В любом случае для этого есть переменная окружения: OMP_NUM_THREADS который может быть проще использовать в сочетании со скриптом запуска / оболочки

Вы можете рассмотреть одну вещь: помимо определенного количества потоков, вы можете насыщать шину памяти, и никакое увеличение потоков [или ядер] не улучшит производительность и, фактически, может снизить производительность.

Из этого вопроса: Увеличивать два целых числа с помощью CAS есть ссылка на видео-разговор с CppCon 2015, который состоит из двух частей: https://www.youtube.com/watch?v=lVBvHbJsg5Y а также https://www.youtube.com/watch?v=1obZeHnAwz4

Они около 1,5 часов каждый, но, IMO, это того стоит.

В своем выступлении докладчик [который провел большую многопоточную / многоядерную оптимизацию] говорит, что из его опыта шина / система памяти имеет тенденцию насыщаться примерно после четырех потоков.

2

Другие решения

Hyper-Threading — это реализация Intel одновременная многопоточность (SMT). Современные процессоры AMD не поддерживают SMT (в семействе микроархитектур Bulldozer есть еще что-то, что AMD называет кластерной многопоточностью, но в микроархитектуре Zen предполагается наличие SMT). OpenMP не имеет встроенной поддержки для обнаружения SMT.

Если вам нужна общая функция для обнаружения Hyper-Threading, вам необходимо поддерживать процессоры разных поколений и убедиться, что процессор является процессором Intel, а не AMD. Для этого лучше всего использовать библиотеку.

Но вы можете создать функцию с использованием OpenMP, которая работает для многих современных процессоров Intel, как я описал Вот.

Следующий код будет подсчитывать количество физических ядер на современных процессорах Intel (он работал на каждом процессоре Intel, на котором я его пробовал). Вы должны связать нити, чтобы заставить это работать. С GCC вы можете использовать
export OMP_PROC_BIND=true в противном случае вы можете связать с кодом (что я и делаю).

Обратите внимание, что я не уверен, что этот метод надежен с VirtualBox. В VirtualBox на 4-ядерном / 8-процессорном процессоре с логическим процессором и Windows в качестве хоста и Linux в качестве догадки устанавливают количество ядер для виртуальной машины на 4, этот код сообщает о 2 ядрах, а / proc / cpuinfo показывает, что два ядра фактически являются логическими процессорами.

#include <stdio.h>

//cpuid function defined in instrset_detect.cpp by Agner Fog (2014 GNU General Public License)
//http://www.agner.org/optimize/vectorclass.zip

// Define interface to cpuid instruction.
// 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) {
#if defined (_MSC_VER) || defined (__INTEL_COMPILER)       // Microsoft or Intel compiler, intrin.h included

__cpuidex(output, functionnumber, 0);                  // intrinsic function for CPUID

#elif defined(__GNUC__) || defined(__clang__)              // use inline assembly, Gnu/AT&T syntax

int a, b, c, d;
__asm("cpuid" : "=a"(a),"=b"(b),"=c"(c),"=d"(d) : "a"(functionnumber),"c"(0) : );
output[0] = a;
output[1] = b;
output[2] = c;
output[3] = d;

#else                                                      // unknown platform. try inline assembly with masm/intel syntax

__asm {
mov eax, functionnumber
xor ecx, ecx
cpuid;
mov esi, output
mov [esi],    eax
mov [esi+4],  ebx
mov [esi+8],  ecx
mov [esi+12], edx
}

#endif
}

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

int main(void) {
printf("cores %d\n", getNumCores());
}
0

По вопросам рекламы [email protected]