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

В OpenMP (параллельный для) в g ++ 4.7 не очень эффективен? 2,5x при 5x ЦП, я определил, что производительность моей программы варьируется от 11 до 13 (в основном всегда выше 12 с, а иногда и ниже 13,4 с) при примерно 500% ЦП при использовании #pragma omp parallel for по умолчанию и Ускорение OpenMP составляет всего 2,5 раза на 5-кратном ЦП с 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 ++ - 4.7 в Debian 7 wheezy и Xeon E3-1240v3, поэтому этот вопрос касается просто сокращения num_threads() количества ядер.)


person cnst    schedule 30.04.2016    source источник
comment
Нет, нет простого способа сделать это полностью автоматическим. Но есть stackoverflow.com/q/2901694/620382 + omp_set_num_threads . Я снова рекомендую вручную контролировать конфигурацию потоков в каждой системе, если это возможно.   -  person Zulan    schedule 30.04.2016
comment
Почему этот вопрос был отклонен ?!   -  person cnst    schedule 02.05.2016


Ответы (2)


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

Поскольку Linux может это обнаружить [и по ссылке в комментарии Zulan], информация также доступна из инструкции 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, оно того стоит.

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

person Craig Estey    schedule 30.04.2016

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());
}
person Z boson    schedule 02.05.2016