Резкое замедление OpenMP для определенного номера потока

Я запустил программу OpenMP для выполнения метода Якоби, и она работала очень хорошо, 2 потока выполнялись немного быстрее, чем в 2 раза по сравнению с 1 потоком, а 4 потока — в 2 раза быстрее, чем 1 поток. Я чувствовал, что все работает отлично... пока не достиг ровно 20, 22 и 24 потоков. Я продолжал ломать его, пока у меня не появилась эта простая программа

#include <stdio.h>
#include <omp.h>

int main(int argc, char *argv[]) {
    int i, n, maxiter, threads, nsquared, execs = 0;
    double begin, end;

    if (argc != 4) {
        printf("4 args\n");
        return 1;
    }

    n = atoi(argv[1]);
    threads = atoi(argv[2]);
    maxiter = atoi(argv[3]);
    omp_set_num_threads(threads);
    nsquared = n * n;

    begin = omp_get_wtime();
    while (execs < maxiter) {

#pragma omp parallel for
        for (i = 0; i < nsquared; i++) {
            //do nothing
        }
        execs++;
    }
    end = omp_get_wtime();

    printf("%f seconds\n", end - begin);

    return 0;
}

И вот некоторый вывод для разных номеров потоков:

./a.out 500 1 1000
    0.6765799 seconds

./a.out 500 8 1000
    0.0851808 seconds

./a.out 500 20 1000
    19.5467 seconds

./a.out 500 22 1000
    21.2296 seconds

./a.out 500 24 1000
    20.1268 seconds

./a.out 500 26 1000
    0.1363 seconds

Я бы понял большое замедление, если бы оно продолжалось для всех потоков, следующих за 20, потому что я полагал, что это будет накладным расходом потока (хотя я чувствовал, что это было немного экстремально). Но даже изменение n оставляет времена 20, 22 и 24 неизменными. Изменение maxiter на 100 уменьшает его примерно до 1,9 секунды, 2,2 секунды, ..., что означает, что замедление вызывает только создание потока, а не внутренняя итерация.

Это как-то связано с попыткой ОС создать потоки, которых у нее нет? Если это что-то значит, omp_get_num_procs() возвращает 24, и это на процессорах Intel Xeon (значит, 24 включает гиперпоточность?)

Спасибо за помощь.


person Ryan Rossiter    schedule 15.02.2014    source источник
comment
Какой компилятор и опции вы используете? Есть ли у вас оптимизация (например, -O3 с GCC или /O2 с MSVC)? Я не думаю, что это интересно, если оптимизация не используется.   -  person Z boson    schedule 16.02.2014
comment
@Zboson Первоначально я использовал GCC без оптимизации (полагая, что оптимизация испортит мою урезанную версию), но теперь, когда я дал GCC -O3, проблема все еще возникает. Теперь я еще больше запутался.   -  person Ryan Rossiter    schedule 16.02.2014
comment
Есть ли какие-либо другие задачи, работающие в системе все время? Ваша система имеет 12 физических ядер (и 24 логических ядра). Любая задача, выполненная на 100%, может сильно повлиять на два потока, и всем остальным потокам придется ждать завершения медленного.   -  person Z boson    schedule 16.02.2014
comment
@Zboson Top показывает, что один процесс занимает 100% 1 ядра. Поскольку это общий сервер, я не могу контролировать процесс 1. Но почему у него нет проблем с запуском 26+ потоков? Не повлияет ли на это такое же замедление?   -  person Ryan Rossiter    schedule 16.02.2014


Ответы (1)


Я подозреваю, что проблема связана с тем, что один поток работает на 100% на одном ядре. Из-за гиперпоточности это действительно потребляет два потока. Вам нужно найти ядро, которое вызывает это, и попытаться исключить его. Предположим, это потоки 20 и 21 (вы сказали, что он начинается с 20 в вашем вопросе - вы уверены в этом?). Попробуйте что-нибудь вроде этого

GOMP_CPU_AFFINITY = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 22 23

Я никогда не использовал это раньше, поэтому вам, возможно, придется немного прочитать об этом, чтобы понять это правильно. Привязка OpenMP и ЦП Возможно, вам потребуется сначала перечислить четные потоки, а затем нечетные (например, 0 2 4 ... 22 1 3 5 ...) в этом случае я не уверен, что исключить (Редактировать: решение было: export GOMP_CPU_AFFINITY="0-17 20-24. См. Комментарии).

Что касается того, почему 26 потоков не будут иметь проблемы, я могу только догадываться. OpenMP может перенести потоки на другие ядра. Ваша система может запускать 24 логических потока. Я никогда не находил причин устанавливать количество потоков на значение, превышающее количество логических потоков (на самом деле в моем коде умножения матриц я устанавливаю количество потоков равным количеству физических ядер, поскольку гиперпоточность дает худший результат ). Возможно, когда вы устанавливаете количество потоков на значение, превышающее количество логических ядер, OpenMP решает, что можно переносить потоки по своему усмотрению. Если он перенесет ваши потоки из ядра, работающего на 100%, проблема может исчезнуть. Вы можете проверить это, отключив миграцию потоков с помощью OMP_PROC_BIND.

person Z boson    schedule 16.02.2014
comment
Ух ты. Оказалось, что 18 тоже не работает, поэтому я сделал export GOMP_CPU_AFFINITY="0-17 20-24" (из здесь) и все заработало. Итак, если я пытаюсь работать максимально быстро, я установлю количество потоков на 12 или 24? Большое спасибо, кстати. - person Ryan Rossiter; 17.02.2014
comment
@RyanRossiter, я рад, что мы нашли решение. Хотелось бы, чтобы у меня был лучший ответ для 26 потоков. Думаю, вы можете проверить [OMP_PROC_BIND] (gcc.gnu.org/onlinedocs/libgomp /GOMP_005fCPU_005fAFFINITY.html). Возможно, вы сможете отключить миграцию потоков. Если вы отключите его, 26 потоков могут иметь ту же проблему. - person Z boson; 17.02.2014
comment
@RyanRossiter, чтобы ответить на ваш вопрос о 12 или 24 потоках (хотя в вашем случае я думаю, что это 11 или 22, поскольку одно ядро ​​​​уже занято), вы должны проверить и посмотреть. Скорее всего вашему коду подойдет гиперпоточность (HT). HT хорошо работает, когда в вашем коде много остановок процессора (промахи кеша, цепочки зависимостей, ...), что обычно и бывает (именно поэтому Intel создала гиперпоточность). Когда ЦП останавливается, HT переключает задачи, чтобы попытаться сделать что-то еще во время простоя. На самом деле сложно написать код, который не останавливает процессор. Использовать HT проще, чем оптимизировать код. - person Z boson; 17.02.2014