Моя системная настройка: система Linux, 12 ядер, изолированные ядра 2–11. Использование ядер 0 и 1 какой-то другой программой почти на 100%. Все остальные ядра простаивают.
export GOMP_CPU_AFFINITY=2,3,4
export OMP_NUM_THREADS=3
taskset -c $GOMP_CPU_AFFINITY perf stat -d ./test_openmp
вывод:
Performance counter stats for './test_openmp':
47,654.74 msec task-clock:u # 2.981 CPUs utilized
0 context-switches:u # 0.000 /sec
0 cpu-migrations:u # 0.000 /sec
115,358 page-faults:u # 2.421 K/sec
159,245,881,934 cycles:u # 3.342 GHz
250,009,309,156 instructions:u # 1.57 insn per cycle
20,002,132,172 branches:u # 419.730 M/sec
117,268 branch-misses:u # 0.00% of all branches
110,002,614,320 L1-dcache-loads:u # 2.308 G/sec
10,796,435,741 L1-dcache-load-misses:u # 9.81% of all L1-dcache accesses
0 LLC-loads:u # 0.000 /sec
0 LLC-load-misses:u # 0.00% of all LL-cache accesses
15.986638336 seconds time elapsed
47.175831000 seconds user
0.414928000 seconds sys
export GOMP_CPU_AFFINITY=1,2,3,4
export OMG_NUM_THREADS=4
taskset -c $GOMP_CPU_AFFINITY perf stat -d ./test_openmp
результат
pid: 4118342
Performance counter stats for './test_openmp':
48,241.03 msec task-clock:u # 1.072 CPUs utilized
0 context-switches:u # 0.000 /sec
0 cpu-migrations:u # 0.000 /sec
119,879 page-faults:u # 2.485 K/sec
161,605,704,451 cycles:u # 3.350 GHz
250,011,376,400 instructions:u # 1.55 insn per cycle
20,002,726,448 branches:u # 414.641 M/sec
118,657 branch-misses:u # 0.00% of all branches
110,002,938,510 L1-dcache-loads:u # 2.280 G/sec
10,796,444,713 L1-dcache-load-misses:u # 9.81% of all L1-dcache accesses
0 LLC-loads:u # 0.000 /sec
0 LLC-load-misses:u # 0.00% of all LL-cache accesses
45.012033357 seconds time elapsed
47.764469000 seconds user
0.399934000 seconds sys
Мой вопрос: почему во второй раз я назначил программе еще одно ядро (ядро 1), но время работы должно быть больше (15,98 секунды против 45,01 секунды), а загрузка процессора очень низкая (2,98 против 1,07)
Вот тестовый код, который я запустил.
#include <iostream>
#include <cstdint>
#include <unistd.h>
constexpr int64_t N = 100000;
int m = N;
int n = N;
int main() {
double* a = new double[N];
double* c = new double[N];
double* b = new double[N*N];
std::cout << "pid: " << getpid() << std::endl;
#pragma omp parallel for default(none) shared(m,n,a,b,c)
for (int i=0; i<m; i++) {
double sum = 0.0;
for (int j=0; j<n; j++)
sum += b[i+j*N]*c[j];
a[i] = sum;
}
return 0;
}
Я так понимаю одно ядро уже занято. Но почему бы openmp просто не распределить больше рабочей нагрузки на ядро Idel и не выделить меньше работы на занятое ядро, чтобы общая загрузка процессора составляла около 3?
Примечание: откажитесь от явных вызовов new
/delete
(у вас уже есть утечка памяти). double* a = new double[N];
-> std::vector<double> a(N);
Каковы ваши параметры загрузки ядра для изоляции этих ядер? Вы можете попробовать запустить программу с помощью планировщика реального времени (chrt -r ....)
Вы сказали ему использовать четыре потока, поэтому он будет использовать четыре, простаивающих ядер недостаточно для запуска четырех потоков, поэтому у вас возникнут конфликты и снизится производительность.
Поскольку вы смотрите на производительность, вам следует убедиться, что ваши вычисления имеют какой-либо видимый побочный эффект. Компиляторы могут быть весьма агрессивными в устранении мертвого кода. Обычно достаточно напечатать a[0]
, a[N/2]
и a[N-1]
.
Если вы не указываете расписание для цикла совместной работы, расписание определяется реализацией. Большинство реализаций выбирают статическое расписание, поскольку оно обеспечивает минимальные затраты времени выполнения для большинства рабочих нагрузок. Статическое расписание распределяет одинаковое количество итераций по каждому потоку.
В вашем случае вы специально хотите, чтобы openmp по-разному распределял работу между потоками. Попробуйте добавить schedule(dynamic)
к параллельной директиве.
Вы также можете выбрать schedule(runtime)
и управлять расписанием, устанавливая переменную среды для каждого выполнения.
Большое спасибо, этот ответ полностью решил мою проблему.
Потому что он пытался использовать ядро, которое, по вашим словам, используется где-то еще, и поэтому все, что выполняется на этом ядре, будет намного медленнее.