2016-04-30 2 views
4

В Is OpenMP (parallel for) in g++ 4.7 not very efficient? 2.5x at 5x CPU я определил, что производительность моей программы варьируется от 11 до 13 лет (в основном всегда выше 12 секунд, а иногда и медленнее, чем 13,4 s) на частоте около 500% CPU при использовании значения по умолчанию #pragma omp parallel for, а скорость OpenMP составляет всего 2,5x при 5x CPU w/g++-4.7 -O3 -fopenmp, на 4-ядерном 8-поточном Xeon.OpenMP: не используйте гиперпотоки (половина `num_threads()` w/hyperthreading)

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

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

(Существует аналогичный вопрос, Poor performance due to hyper-threading with OpenMP: how to bind threads to cores, но в моем тестировании, я обнаружил, что простое сокращение от 8 до 4 потоков как-то уже делает эту работу ж/г ++ - 4.7 на Debian 7 хриплым и Xeon E3-1240v3 , так что, этот вопрос связан только с уменьшением num_threads() до количества ядер.)

+3

Нет, нет простого способа сделать это полностью автоматическим. Но есть http://stackoverflow.com/q/2901694/620382 + 'omp_set_num_threads'. Опять же, я рекомендую вручную управлять конфигурацией потоков в каждой системе, если это возможно. – Zulan

+0

Почему этот вопрос был опущен? – cnst

ответ

2

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

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

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

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

От этого вопроса: Atomically increment two integers with CAS есть ссылка на видео разговор с CppCon 2015, который состоит из двух частей: https://www.youtube.com/watch?v=lVBvHbJsg5Y и https://www.youtube.com/watch?v=1obZeHnAwz4

Они около 1,5 часа каждый, но, ИМО, хорошо стоит.

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

0

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

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

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

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

Обратите внимание, что я не уверен, что этот метод является надежным с VirtualBox. С VirtualBox на процессоре с четырьмя ядрами/8 с процессором с процессором Core и Windows в качестве хоста и Linux в качестве опции определения количества ядер для VM до 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()); 
}