У меня есть симулятор N-Body, основанный на алгоритме Барнса-Хата, который я многопоточный с использованием OpenMP. Большая часть программы была сделана параллельной, просто добавив #pragma omp parallel for
в нескольких ключевых местах. Это обеспечивает здоровое ускорение, которое хорошо масштабируется с количеством ядер, когда число гравитационных тел меньше пары тысяч.
Поскольку моя программа использует алгоритм Барнса-Хата , в его основе лежит древовидная структура, в 2d - это квадродерево, а в моем случае - октодерево. Я сталкиваюсь с проблемой многопоточности процесса заполнения дерева. Выполнение этого шага в однопоточном режиме не позволяет программе полностью использовать мой процессор. Моя загрузка ЦП фактически снижается при добавлении большего количества тел, потому что больше времени затрачивается на добавление всех тел в октре с использованием только одного ядра.
Прямо сейчас метод добавления одного тела к октодереву выглядит следующим образом:
void octant::addBody(vec3 newPosition, float newMass) {
// Making room for new bodies by dividing if the node is a leaf
if (isLeaf) {
// Subdividing the octant
divide();
// Moving the body already contained
subdivisionEnclosing(this->position)->addBody(this->position, this->mass);
}
// Adding the body to the appropriate subdivision if the node is divided
if (divided) {
// Adding the new body to the appropriate octant
subdivisionEnclosing(newPosition)->addBody(newPosition, newMass);
return;
}
// If the node doesnt yet contain any bodies at all, add the new one
this->position = newPosition;
this->mass = newMass;
// This node only contains one body, so the center of mass is accurate
isLeaf = true;
calculatedCOM = true;
}
Это прекрасно работает, когда вызывается последовательно, но естественно ломается, когда я пытаюсь добавить несколько тел к одному корневому узлу одновременно. Этот код не содержит каких-либо мер по обеспечению безопасности потока объекта-октанта.
В идеале я хотел бы иметь возможность вызывать метод addBody параллельно, используя что-то вроде этого:
#pragma omp parallel for
for (int b = 0; b < bodies.size(); ++b) {
octree->addBody(bodies[b]->getPosition(), bodies[b]->getMass());
}
Я экспериментировал с добавлением #pragma omp critical(name)
к частям метода, где изменяются данные, и #pragma omp single
, где узел делится. Ничто из того, что я пробовал, не могло предотвратить немедленное падение.
Я также создал метод, который добавляет тела партиями. Он взял вектор объектов тела, отсортировал их по векторам в зависимости от того, в какое подразделение они вписываются, и передал эти векторы в свои соответствующие подразделения. Каждое подразделение получило свой собственный поток, и процесс был рекурсивным. Это работало и использовало все мои ядра, но было значительно медленнее. Я думаю, что размещение тел в векторах добавило тонну накладных расходов.
Я довольно новичок в OpenMP и даже новее в концепции безопасности потоков. Как лучше всего решить эту проблему? Я не могу найти очень много примеров потоковых безопасных древовидных структур в Интернете, и ни один из них не использует OpenMP. Каков идеальный подход к заполнению дерева с использованием нескольких потоков? По крайней мере, какие инструменты, по вашему мнению, были бы полезны для работы такого рода вещей
Редактировать: Кто-нибудь знает какие-либо примеры полностью поточно-ориентированной древовидной структуры? Даже если это не в OpenMP, меня прежде всего интересует, как дерево можно добавлять / генерировать / заполнять потокобезопасным способом.