Евгений Крутько, НИЦ «Курчатовский институт».
В докладе на примере программы моделирования динамики движения конструкций по методу конечных элементов рассматриваются возможности и практика распараллеливания вычислений. Речь в нём пойдёт как о технике создания новых вычислительных потоков, так и об использовании стандартов openMP и MPI.
Cайт. Зачем он и каким должен быть, Алексей Иванов, лекция в Школе вебмастеро...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повышения производительности численных расчётов
1. C++
User
Group
Mee-ng,
Nizhniy
Novgorod,
19.04.14
Опыт
внедрения
технологий
параллельных
вычислений
для
повышения
производительности
численных
расчетов
Крутько
Е.С.
НИЦ
«Курчатовский
институт»
e.s.krutko@gmail.com
2. План
презентации
• Рассмотрение
кода
программы
моделирования
динамики
твердого
тела
по
методу
конечных
элементов
• Использование
openmp
• Явное
создание
вычислительных
потоков
• Использование
MPI
• Комбинированный
подход
Репозиторий
проекта:
hnps://github.com/eskrut/directIntegra-onSolver.git
2
3. Кратко
о
теории
M¨q + C ˙q + Kq = f
q = {dx0, dy0, dz0, · · · dxN 1, dyN 1, dzN 1}T
M =
0
B
B
B
@
m0 0 0 0
0 m1 0 0
0 0
... 0
0 0 0 mN 1
1
C
C
C
A
C = ⇠M
˙q =
qt+ t qt t
2 t
, ¨q =
qt+ t 2qt + qt t
2 t
qt+ t(a0M + a1C) = f Kq + a2Mqt (a0M a1C)qt t
Задача
решается
по
методу
к о н е ч н ы х
э л е м е н т о в
с
использованием
явной
схемы
интегрирования
по
времени.
Вся
необходимая
логика
МКЭ
реализована
во
внешней
библиотеке,
необходимо
только
построить
на
ее
основе
решатель.
3
8. Ветка:
Коммит:
Поиск
узкого
места
master
6d51e09
// Time integration loop!
report.createNewProgress("Time integration");!
std::chrono::nanoseconds period1(0), period2(0), period3(0), period4(0);!
while( t < tStop ) { // Time loop!
auto timePoint_0 = Clock::now();!
// Update displacements of kinematically loaded nodes!
// .... //!
auto timePoint_1 = Clock::now();!
for(int nodeCt = 0; nodeCt < numNodes; nodeCt++) {!
// Make multiplication of stiffness matrix over displacement vector!
// .... //!
}!
auto timePoint_2 = Clock::now();!
for(int nodeCt = 0; nodeCt < numNodes; nodeCt++) {!
// Perform finite difference step!
// .... //!
}!
auto timePoint_3 = Clock::now();!
// Make output if required!
// Prepere for next step!
// .... //!
auto timePoint_4 = Clock::now();!
period1 += std::chrono::duration_cast<std::chrono::nanoseconds>(timePoint_1 - timePoint_0);!
period2 += std::chrono::duration_cast<std::chrono::nanoseconds>(timePoint_2 - timePoint_1);!
period3 += std::chrono::duration_cast<std::chrono::nanoseconds>(timePoint_3 - timePoint_2);!
period4 += std::chrono::duration_cast<std::chrono::nanoseconds>(timePoint_4 - timePoint_3);!
} // End time loop!
report.finalizeProgress();!
!
report("Prepare part time : ", period1.count());!
report("Matrix multiplication time : ", period2.count());!
report("Finite difference step time : ", period3.count());!
report("Finilizing part time : ", period4.count());!
!
Вывод:
Prepare part time : 26608769!
Matrix multiplication time : 87985118534!
Finite difference step time : 7031352999!
Finilizing part time : 2039084873! 8
9. OpenMP
OpenMP
(Open
Mul--‐Processing)
—
открытый
стандарт
для
распараллеливания
программ
на
языках
Си,
Си++
и
Фортран.
Описывает
совокупность
директив
компилятора,
библиотечных
процедур
и
переменных
окружения,
которые
предназначены
для
программирования
многопоточных
приложений
на
многопроцессорных
системах
с
общей
памятью
//Wikipedia
#pragma
omp
parallel
#pragma
omp
for
#pragma
omp
sec-ons
#pragma
omp
single
#pragma
omp
parallel
for
#pragma
omp
parallel
sec-ons
#pragma
omp
task
#pragma
omp
master
#pragma
omp
cri-cal
#pragma
omp
barrier
void
omp_set_num_threads(int
);
int
omp_get_num_threads(void);
int
omp_get_max_threads(void);
int
omp_get_thread_num(void);
int
omp_get_num_procs(void);
int
omp_in_parallel(void);
void
omp_set_dynamic(int
);
void
omp_set_lock(omp_lock_t
*);
int
omp_test_lock(omp_lock_t
*);
OMP_SCHEDULE
type[,chunk]
OMP_NUM_THREADS
num
OMP_DYNAMIC
dynamic
OMP_NESTED
nested
OMP_STACKSIZE
size
OMP_WAIT_POLICY
policy
OMP_MAX_ACTIVE_LEVELS
levels
OMP_THREAD_LIMIT
limit
9
10. Ветка:
Коммит:
Первая
попытка
с
openMP
(не
работает)
openMP
99940bc
CMakeLists.txt!
!
find_package(OpenMP REQUIRED)!
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")!
!
main.cpp!
!
#include <omp.h>!
// ... //!
// Stiffness matrix iteration halper!
std::unique_ptr<sbfMatrixIterator> iteratorPtr(stiff->createIterator());!
sbfMatrixIterator *iterator = iteratorPtr.get();!
!
// Time integration loop!
report.createNewProgress("Time integration");!
while( t < tStop ) { // Time loop!
// Update displacements of kinematically loaded nodes!
// ... //!
#pragma omp parallel for!
for(int nodeCt = 0; nodeCt < numNodes; nodeCt++) {!
// Make multiplication of stiffness matrix over displacement vector!
iterator->setToRow(nodeCt);!
// ... //!
while(iterator->isValid()) {!
// ... //!
iterator->next();!
}!
// Perform finite difference step!
for(int ct = 0; ct < 3; ct++) {!
temp=force.data(nodeCt, ct) // ... //;!
displ_p1.data(nodeCt, ct) = temp/(a0*mass.data(nodeCt, ct)// ... //);!
}!
}!
// Make output if required!
// Prepere for next step!
} // End time loop 10
11. Ветка:
Коммит:
Нормальная
работа
openMP
openMP
3f10371
// Time integration loop!
report.createNewProgress("Time integration");!
while( t < tStop ) { // Time loop!
double tmp[3], temp;!
// Update displacements of kinematically loaded nodes!
// ... //!
#pragma omp parallel private(temp)!
{!
// Stiffness matrix iteration halper!
std::unique_ptr<sbfMatrixIterator> iteratorPtr(stiff->createIterator());!
sbfMatrixIterator *iterator = iteratorPtr.get();!
#pragma omp for!
for(int nodeCt = 0; nodeCt < numNodes; nodeCt++) {!
// Make multiplication of stiffness matrix over displacement vector!
iterator->setToRow(nodeCt);!
// ... //!
while(iterator->isValid()) {!
// ... //!
iterator->next();!
}!
// Perform finite difference step!
for(int ct = 0; ct < 3; ct++) {!
temp=force.data(nodeCt, ct) - rezKU.data(nodeCt, ct) +!
a2*mass.data(nodeCt, ct)*displ.data(nodeCt, ct) -!
(a0*mass.data(nodeCt, ct) - a1*demp.data(nodeCt, ct))*displ_m1.data(nodeCt, ct);!
displ_p1.data(nodeCt, ct) = temp/(a0*mass.data(nodeCt, ct) + a1*demp.data(nodeCt, ct));!
}!
}//End of #pragma omp for!
}//End of #pragma omp parallel!
// Make output if required!
// Prepere for next step!
} // End time loop
11
22. Ветка:
Коммит:
Использование
MPI
MPI
b42b24a
// Time integration loop!
int nodeStart = numNodes/numProcs*procID;!
int nodeStop = procID != numProcs - 1 ? numNodes/numProcs*(procID+1) : numNodes;!
if ( procID == 0 ) report.createNewProgress("Time integration");!
while( t < tStop ) { // Time loop!
// Update displacements of kinematically loaded nodes!
// ... //!
!
MPI_Bcast(displ.data(), numNodes*3, MPI_DOUBLE, 0, MPI_COMM_WORLD);!
rezKU.null();!
reduceBuf.null();!
for(int nodeCt = nodeStart; nodeCt < nodeStop; nodeCt++) {!
// Make multiplication of stiffness matrix over displacement vector!
iterator->setToRow(nodeCt);!
while(iterator->isValid()) { /* ... */ iterator->next(); }!
// Perform finite difference step!
for(int ct = 0; ct < 3; ct++) {!
// ... //!
reduceBuf.data()[3*nodeCt+ct] = temp/(a0*mass.data()[3*nodeCt+ct] + a1*demp.data()[3*nodeCt+ct]);!
}!
}!
MPI_Allreduce(reduceBuf.data(), displ_p1.data(), numNodes*3, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);!
!
// Make output if required!
if ( procID == 0 ) if(t >= tNextOut) { /* ... */ }!
// Prepere for next step!
t += dt;!
displ_m1.copyData(displ); displ.copyData(displ_p1);!
} // End time loop!
if ( procID == 0 ) report.finalizeProgress();!
!
MPI_Finalize();!
22
23. Ветка:
Коммит:
MPI
+
потоки
MPI_threads
7b033e3
int nodeStart = numNodes/numProcs*procID;!
int nodeStop = procID != numProcs - 1 ? numNodes/numProcs*(procID+1) : numNodes;!
!
for(int ct = 0; ct < numThreads; ct++) {!
eventsStart.push_back(new event);!
eventsStop.push_back(new event);!
int threadStartNode = nodeStart + (nodeStop - nodeStart)/numThreads*ct;!
int threadStopNode = nodeStart + (nodeStop - nodeStart)/numThreads*(ct+1);!
if(ct == numThreads-1) threadStopNode = nodeStop;!
threads[ct] = std::thread(std::bind(procFunc, threadStartNode, threadStopNode, eventsStart[ct],
eventsStop[ct]));!
threads[ct].detach();!
}!
for(auto ev : eventsStop) ev->wait();!
!
// Time integration loop!
if ( procID == 0 ) report.createNewProgress("Time integration");!
while( t < tStop ) { // Time loop!
// Update displacements of kinematically loaded nodes!
!
MPI_Bcast(displ.data(), numNodes*3, MPI_DOUBLE, 0, MPI_COMM_WORLD);!
rezKU.null(); reduceBuf.null();!
for(auto ev : eventsStart) ev->set();!
for(auto ev : eventsStop) ev->wait();!
MPI_Allreduce(reduceBuf.data(), displ_p1.data(), numNodes*3, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);!
!
// Make output if required!
if ( procID == 0 ) if(t >= tNextOut) { /* ... */}!
// Prepere for next step!
t += dt; displ_m1.copyData(displ); displ.copyData(displ_p1);!
} // End time loop!
if ( procID == 0 ) report.finalizeProgress();
23
24. Вместо
выводов
Один
поток
openMP
threads
MPI
MPI
+
threads
97
20
21
42
31
IMHO:
• При
современном
развитии
вычислительной
техники
разработчику
непростительно
написание
не
параллельных
программ,
особенно
в
области
расчетов
• Современные
стандарты
и
их
реализации
позволяют
относительно
быстро
внедрять
их
в
существующие
или
вновь
создаваемые
проекты
• Хотя,
возможно
в
ближайшем
будущем
компиляторы
научатся
делать
все
за
нас
:)
24