Skip to content

Commit 6e89138

Browse files
authored
perf: parallelize periodic image generation (deepmodeling#7808)
1 parent 095b2d5 commit 6e89138

2 files changed

Lines changed: 243 additions & 33 deletions

File tree

source/source_cell/module_neighlist/neighbor_search.cpp

Lines changed: 109 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "source_cell/module_neighlist/neighbor_search.h"
22
#include <cmath>
33
#include <algorithm>
4+
#include <cstdint>
45
#include <limits>
56
#include <cassert>
67
#include <array>
@@ -112,13 +113,14 @@ void NeighborSearch::init(const AtomProvider& ucell, double sr)
112113
{
113114
for (int j = 0; j < ucell.get_na(i); j++)
114115
{
116+
const ModuleBase::Vector3<double> position = ucell.get_tau(i, j);
115117
const ModuleNeighList::LocalAtomIndex atom_count
116118
= ModuleNeighList::checked_local_atom_index(all_atoms_.size(),
117119
"NeighborSearch atom id");
118120
NeighborAtom atom(
119-
ucell.get_tau(i,j).x,
120-
ucell.get_tau(i,j).y,
121-
ucell.get_tau(i,j).z,
121+
position.x,
122+
position.y,
123+
position.z,
122124
i,
123125
j,
124126
atom_count
@@ -189,37 +191,111 @@ void NeighborSearch::check_expand_condition(const AtomProvider& ucell, int& glay
189191

190192
void NeighborSearch::set_member_variables(const AtomProvider& ucell, int glayerX_minus, int glayerX, int glayerY_minus, int glayerY, int glayerZ_minus, int glayerZ)
191193
{
192-
ModuleBase::Vector3<double> vec1(ucell.get_latvec().e11, ucell.get_latvec().e12, ucell.get_latvec().e13);
193-
ModuleBase::Vector3<double> vec2(ucell.get_latvec().e21, ucell.get_latvec().e22, ucell.get_latvec().e23);
194-
ModuleBase::Vector3<double> vec3(ucell.get_latvec().e31, ucell.get_latvec().e32, ucell.get_latvec().e33);
194+
if (glayerX_minus < 0 || glayerX < 0 || glayerY_minus < 0 || glayerY < 0
195+
|| glayerZ_minus < 0 || glayerZ < 0)
196+
{
197+
throw std::invalid_argument("NeighborSearch periodic image layers must be non-negative.");
198+
}
195199

196-
for (int ix = -glayerX_minus; ix < glayerX; ix++)
200+
const ModuleBase::Matrix3& lattice = ucell.get_latvec();
201+
const ModuleBase::Vector3<double> vec1(lattice.e11, lattice.e12, lattice.e13);
202+
const ModuleBase::Vector3<double> vec2(lattice.e21, lattice.e22, lattice.e23);
203+
const ModuleBase::Vector3<double> vec3(lattice.e31, lattice.e32, lattice.e33);
204+
205+
const std::size_t image_count_x
206+
= ModuleNeighList::checked_size_sum(static_cast<std::size_t>(glayerX_minus),
207+
static_cast<std::size_t>(glayerX),
208+
"NeighborSearch x image count");
209+
const std::size_t image_count_y
210+
= ModuleNeighList::checked_size_sum(static_cast<std::size_t>(glayerY_minus),
211+
static_cast<std::size_t>(glayerY),
212+
"NeighborSearch y image count");
213+
const std::size_t image_count_z
214+
= ModuleNeighList::checked_size_sum(static_cast<std::size_t>(glayerZ_minus),
215+
static_cast<std::size_t>(glayerZ),
216+
"NeighborSearch z image count");
217+
const std::size_t image_count_yz
218+
= ModuleNeighList::checked_size_product(image_count_y,
219+
image_count_z,
220+
"NeighborSearch yz image count");
221+
const std::size_t image_count
222+
= ModuleNeighList::checked_size_product(image_count_x,
223+
image_count_yz,
224+
"NeighborSearch periodic image count");
225+
if (image_count == 0)
197226
{
198-
for (int iy = -glayerY_minus; iy < glayerY; iy++)
199-
{
200-
for (int iz = -glayerZ_minus; iz < glayerZ; iz++)
201-
{
202-
if(ix==0 && iy==0 && iz==0)
203-
{
204-
continue;
205-
}
206-
for (int i = 0; i < ucell.get_ntype(); i++)
207-
{
208-
for (int j = 0; j < ucell.get_na(i); j++)
209-
{
210-
double atom_x = ucell.get_tau(i,j).x + vec1[0] * ix + vec2[0] * iy + vec3[0] * iz;
211-
double atom_y = ucell.get_tau(i,j).y + vec1[1] * ix + vec2[1] * iy + vec3[1] * iz;
212-
double atom_z = ucell.get_tau(i,j).z + vec1[2] * ix + vec2[2] * iy + vec3[2] * iz;
213-
214-
const ModuleNeighList::LocalAtomIndex atom_count
215-
= ModuleNeighList::checked_local_atom_index(all_atoms_.size(),
216-
"NeighborSearch atom id");
217-
NeighborAtom atom(atom_x, atom_y, atom_z, i, j, atom_count);
218-
ghost_atoms_.push_back(atom);
219-
all_atoms_.push_back(atom);
220-
}
221-
}
222-
}
223-
}
227+
throw std::invalid_argument("NeighborSearch periodic image range must contain the primary cell.");
228+
}
229+
230+
const std::size_t origin_xy
231+
= ModuleNeighList::checked_size_sum(
232+
ModuleNeighList::checked_size_product(static_cast<std::size_t>(glayerX_minus),
233+
image_count_y,
234+
"NeighborSearch primary image index"),
235+
static_cast<std::size_t>(glayerY_minus),
236+
"NeighborSearch primary image index");
237+
const std::size_t origin_image
238+
= ModuleNeighList::checked_size_sum(
239+
ModuleNeighList::checked_size_product(origin_xy,
240+
image_count_z,
241+
"NeighborSearch primary image index"),
242+
static_cast<std::size_t>(glayerZ_minus),
243+
"NeighborSearch primary image index");
244+
245+
const std::size_t base_atom_count = inside_atoms_.size();
246+
const std::size_t ghost_image_count = image_count - 1;
247+
const std::size_t generated_atom_count
248+
= ModuleNeighList::checked_size_product(ghost_image_count,
249+
base_atom_count,
250+
"NeighborSearch generated atom count");
251+
const std::size_t final_atom_count
252+
= ModuleNeighList::checked_size_sum(base_atom_count,
253+
generated_atom_count,
254+
"NeighborSearch total atom count");
255+
if (final_atom_count > static_cast<std::size_t>(std::numeric_limits<ModuleNeighList::LocalAtomIndex>::max()))
256+
{
257+
throw std::overflow_error("NeighborSearch total atom count exceeds local atom index range.");
258+
}
259+
if (generated_atom_count == 0)
260+
{
261+
return;
262+
}
263+
264+
const NeighborAtom placeholder(0.0, 0.0, 0.0, 0, 0, 0);
265+
all_atoms_.insert(all_atoms_.end(), generated_atom_count, placeholder);
266+
ghost_atoms_.assign(generated_atom_count, placeholder);
267+
268+
// Thread creation costs more than it saves for small unit cells.
269+
const std::size_t parallel_threshold = 100000;
270+
#ifdef _OPENMP
271+
#pragma omp parallel for schedule(static) if(generated_atom_count >= parallel_threshold)
272+
#endif
273+
for (std::int64_t generated_index = 0;
274+
generated_index < static_cast<std::int64_t>(generated_atom_count);
275+
++generated_index)
276+
{
277+
const std::size_t output_index = static_cast<std::size_t>(generated_index);
278+
const std::size_t ghost_image = output_index / base_atom_count;
279+
const std::size_t base_atom = output_index % base_atom_count;
280+
const std::size_t image = ghost_image < origin_image ? ghost_image : ghost_image + 1;
281+
const std::size_t image_x = image / image_count_yz;
282+
const std::size_t image_yz = image % image_count_yz;
283+
const std::size_t image_y = image_yz / image_count_z;
284+
const std::size_t image_z = image_yz % image_count_z;
285+
const double ix = static_cast<double>(image_x) - static_cast<double>(glayerX_minus);
286+
const double iy = static_cast<double>(image_y) - static_cast<double>(glayerY_minus);
287+
const double iz = static_cast<double>(image_z) - static_cast<double>(glayerZ_minus);
288+
289+
const NeighborAtom& source = inside_atoms_[base_atom];
290+
const ModuleNeighList::LocalAtomIndex atom_id
291+
= static_cast<ModuleNeighList::LocalAtomIndex>(base_atom_count + output_index);
292+
const NeighborAtom atom(source.position_x + vec1[0] * ix + vec2[0] * iy + vec3[0] * iz,
293+
source.position_y + vec1[1] * ix + vec2[1] * iy + vec3[1] * iz,
294+
source.position_z + vec1[2] * ix + vec2[2] * iy + vec3[2] * iz,
295+
source.atom_type,
296+
source.atom_index,
297+
atom_id);
298+
ghost_atoms_[output_index] = atom;
299+
all_atoms_[base_atom_count + output_index] = atom;
224300
}
225301
}

source/source_cell/module_neighlist/test/neighbor_search_test.cpp

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,63 @@
77
#include <cstddef>
88
#include <vector>
99

10+
#ifdef _OPENMP
11+
#include <omp.h>
12+
#endif
13+
1014
namespace
1115
{
16+
class CountingAtomProvider : public AtomProvider
17+
{
18+
public:
19+
explicit CountingAtomProvider(const UnitCellLite& ucell) : ucell_(ucell) {}
20+
21+
double get_lat0() const override
22+
{
23+
return ucell_.get_lat0();
24+
}
25+
26+
double get_omega() const override
27+
{
28+
return ucell_.get_omega();
29+
}
30+
31+
const ModuleBase::Matrix3& get_latvec() const override
32+
{
33+
return ucell_.get_latvec();
34+
}
35+
36+
int get_natom() const override
37+
{
38+
return ucell_.get_natom();
39+
}
40+
41+
int get_na(int type) const override
42+
{
43+
return ucell_.get_na(type);
44+
}
45+
46+
int get_ntype() const override
47+
{
48+
return ucell_.get_ntype();
49+
}
50+
51+
ModuleBase::Vector3<double> get_tau(int type, int index) const override
52+
{
53+
++get_tau_calls_;
54+
return ucell_.get_tau(type, index);
55+
}
56+
57+
std::size_t get_tau_calls() const
58+
{
59+
return get_tau_calls_;
60+
}
61+
62+
private:
63+
const UnitCellLite& ucell_;
64+
mutable std::size_t get_tau_calls_ = 0;
65+
};
66+
1267
UnitCellLite make_test_ucell(double lat0,
1368
double omega,
1469
const ModuleBase::Matrix3& latvec,
@@ -46,6 +101,25 @@ std::size_t count_pairs(const NeighborList& list)
46101
}
47102
return pairs;
48103
}
104+
105+
#ifdef _OPENMP
106+
void expect_same_atoms(const std::vector<NeighborAtom>& lhs,
107+
const std::vector<NeighborAtom>& rhs)
108+
{
109+
ASSERT_EQ(lhs.size(), rhs.size());
110+
for (std::size_t i = 0; i < lhs.size(); ++i)
111+
{
112+
EXPECT_DOUBLE_EQ(lhs[i].position_x, rhs[i].position_x) << "atom " << i;
113+
EXPECT_DOUBLE_EQ(lhs[i].position_y, rhs[i].position_y) << "atom " << i;
114+
EXPECT_DOUBLE_EQ(lhs[i].position_z, rhs[i].position_z) << "atom " << i;
115+
EXPECT_EQ(lhs[i].atom_type, rhs[i].atom_type) << "atom " << i;
116+
EXPECT_EQ(lhs[i].atom_index, rhs[i].atom_index) << "atom " << i;
117+
EXPECT_EQ(lhs[i].atom_id, rhs[i].atom_id) << "atom " << i;
118+
EXPECT_EQ(lhs[i].global_id, rhs[i].global_id) << "atom " << i;
119+
EXPECT_EQ(lhs[i].owner_rank, rhs[i].owner_rank) << "atom " << i;
120+
}
121+
}
122+
#endif
49123
} // namespace
50124

51125
TEST(NeighborSearchTest, TwoAtomsNeighbor)
@@ -110,6 +184,66 @@ TEST(NeighborSearchTest, SerialInitOwnsCentralAtomsAndBuildsImages)
110184
}
111185
}
112186

187+
TEST(NeighborSearchTest, SerialInitReadsEachPrimaryCoordinateOnce)
188+
{
189+
UnitCellLite ucell = make_test_ucell(1.0,
190+
1.0,
191+
identity_lattice(),
192+
1,
193+
{2},
194+
{{0.0, 0.0, 0.0}, {0.5, 0.0, 0.0}});
195+
CountingAtomProvider provider(ucell);
196+
197+
NeighborSearch ns;
198+
ns.init(provider, 1.0);
199+
200+
EXPECT_EQ(provider.get_tau_calls(), 2U);
201+
EXPECT_EQ(ns.get_inside_atoms().size(), 2U);
202+
EXPECT_EQ(ns.get_ghost_atoms().size(), 52U);
203+
EXPECT_EQ(ns.get_all_atoms().size(), 54U);
204+
}
205+
206+
#ifdef _OPENMP
207+
TEST(NeighborSearchTest, ParallelImageGenerationMatchesSerialOrder)
208+
{
209+
const int atom_count = 4000;
210+
std::vector<ModuleBase::Vector3<double>> positions;
211+
positions.reserve(atom_count);
212+
for (int atom = 0; atom < atom_count; ++atom)
213+
{
214+
positions.push_back(ModuleBase::Vector3<double>(0.0001 * atom,
215+
0.0002 * atom,
216+
0.0003 * atom));
217+
}
218+
UnitCellLite ucell = make_test_ucell(1.0,
219+
1.0,
220+
identity_lattice(),
221+
1,
222+
{atom_count},
223+
positions);
224+
225+
const int previous_dynamic = omp_get_dynamic();
226+
const int previous_max_threads = omp_get_max_threads();
227+
omp_set_dynamic(0);
228+
omp_set_num_threads(1);
229+
NeighborSearch serial;
230+
serial.init(ucell, 1.0);
231+
232+
omp_set_num_threads(4);
233+
NeighborSearch parallel;
234+
parallel.init(ucell, 1.0);
235+
236+
omp_set_num_threads(previous_max_threads);
237+
omp_set_dynamic(previous_dynamic);
238+
239+
EXPECT_EQ(parallel.get_inside_atoms().size(), static_cast<std::size_t>(atom_count));
240+
EXPECT_EQ(parallel.get_ghost_atoms().size(), 104000U);
241+
expect_same_atoms(serial.get_inside_atoms(), parallel.get_inside_atoms());
242+
expect_same_atoms(serial.get_ghost_atoms(), parallel.get_ghost_atoms());
243+
expect_same_atoms(serial.get_all_atoms(), parallel.get_all_atoms());
244+
}
245+
#endif
246+
113247
TEST(NeighborSearchTest, DistributedInputUsesOwnedCentersAndGhostNeighbors)
114248
{
115249
std::vector<LocalAtom> owned_atoms;

0 commit comments

Comments
 (0)