Skip to content

Commit c134b6b

Browse files
committed
Documentazione
1 parent 86d982f commit c134b6b

2 files changed

Lines changed: 147 additions & 2 deletions

File tree

include/openPMD/Chunk.hpp

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,61 @@ namespace chunk_assignment
9191
PartialAssignment( ChunkTable notAssigned, ChunkTable assigned );
9292
};
9393

94+
/**
95+
* @brief Interface for a chunk distribution strategy.
96+
*
97+
* Used for implementing algorithms that read a ChunkTable as produced
98+
* by BaseRecordComponent::availableChunks() and produce as result a
99+
* ChunkTable that guides data sinks on how to load data into reading
100+
* processes.
101+
*/
94102
struct Strategy
95103
{
104+
/**
105+
* @brief Assign chunks to be loaded to reading processes.
106+
*
107+
* @param partialAssignment Two chunktables, one of unassigned chunks
108+
* and one of chunks that might have already been assigned
109+
* previously.
110+
* Merge the unassigned chunks into the partially assigned table.
111+
* @param in Meta information on writing processes, e.g. hostnames.
112+
* @param out Meta information on reading processes, e.g. hostnames.
113+
* @return ChunkTable A table that assigns chunks to reading processes.
114+
*/
96115
virtual ChunkTable
97116
assign(
98-
PartialAssignment,
117+
PartialAssignment partialAssignment,
99118
RankMeta const & in,
100119
RankMeta const & out ) = 0;
101120

102121
virtual ~Strategy() = default;
103122
};
104123

124+
/**
125+
* @brief A chunk distribution strategy that guarantees no complete
126+
* distribution.
127+
*
128+
* Combine with a full Strategy using the FromPartialStrategy struct to
129+
* obtain a Strategy that works in two phases:
130+
* 1. Apply the partial strategy.
131+
* 2. Apply the full strategy to assign unassigned leftovers.
132+
*
133+
*/
105134
struct PartialStrategy
106135
{
136+
/**
137+
* @brief Assign chunks to be loaded to reading processes.
138+
*
139+
* @param partialAssignment Two chunktables, one of unassigned chunks
140+
* and one of chunks that might have already been assigned
141+
* previously.
142+
* Merge the unassigned chunks into the partially assigned table.
143+
* @param in Meta information on writing processes, e.g. hostnames.
144+
* @param out Meta information on reading processes, e.g. hostnames.
145+
* @return PartialAssignment Two chunktables, one of leftover chunks
146+
* that were not assigned and one that assigns chunks to
147+
* reading processes.
148+
*/
107149
virtual PartialAssignment
108150
assign(
109151
PartialAssignment,
@@ -120,6 +162,18 @@ namespace chunk_assignment
120162
RankMeta const & rankMetaOut,
121163
Strategy & strategy );
122164

165+
/**
166+
* @brief Combine a PartialStrategy and a Strategy to obtain a Strategy
167+
* working in two phases.
168+
*
169+
* 1. Apply the PartialStrategy to obtain a PartialAssignment.
170+
* This may be a heuristic that will not work under all circumstances,
171+
* e.g. trying to distribute chunks within the same compute node.
172+
* 2. Apply the Strategy to assign leftovers.
173+
* This guarantees correctness in case the heuristics in the first phase
174+
* were not applicable e.g. due to a suboptimal setup.
175+
*
176+
*/
123177
struct FromPartialStrategy : Strategy
124178
{
125179
FromPartialStrategy(
@@ -134,12 +188,25 @@ namespace chunk_assignment
134188
std::unique_ptr< Strategy > m_secondPass;
135189
};
136190

191+
/**
192+
* @brief Simple strategy that assigns produced chunks to reading processes
193+
* in a round-Robin manner.
194+
*
195+
*/
137196
struct RoundRobin : Strategy
138197
{
139198
ChunkTable
140199
assign( PartialAssignment, RankMeta const & in, RankMeta const & out );
141200
};
142201

202+
/**
203+
* @brief Strategy that assigns chunks to be read by processes within
204+
* the same host that produced the chunk.
205+
*
206+
* The distribution strategy within one such chunk can be flexibly
207+
* chosen.
208+
*
209+
*/
143210
struct ByHostname : PartialStrategy
144211
{
145212
ByHostname( std::unique_ptr< Strategy > withinNode );
@@ -152,6 +219,16 @@ namespace chunk_assignment
152219
std::unique_ptr< Strategy > m_withinNode;
153220
};
154221

222+
/**
223+
* @brief Slice the n-dimensional dataset into hyperslabs and distribute
224+
* chunks according to them.
225+
*
226+
* This strategy only produces chunks in the returned ChunkTable for the
227+
* calling parallel process.
228+
* Incoming chunks are intersected with the hyperslab and assigned to the
229+
* current parallel process in case this intersection is non-empty.
230+
*
231+
*/
155232
struct ByCuboidSlice : Strategy
156233
{
157234
ByCuboidSlice(
@@ -170,6 +247,18 @@ namespace chunk_assignment
170247
unsigned int mpi_rank, mpi_size;
171248
};
172249

250+
/**
251+
* @brief Strategy that tries to assign chunks in a balanced manner without
252+
* arbitrarily cutting chunks.
253+
*
254+
* Idea:
255+
* Calculate the ideal amount of data to be loaded per parallel process
256+
* and cut chunks s.t. no chunk is larger than that ideal size.
257+
* The resulting problem is an instance of the Bin-Packing problem which
258+
* can be solved by a factor-2 approximation, meaning that a reading process
259+
* will be assigned at worst twice the ideal amount of data.
260+
*
261+
*/
173262
struct BinPacking : Strategy
174263
{
175264
ChunkTable

src/Chunk.cpp

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ namespace chunk_assignment
141141
ChunkTable & sinkChunks = partialAssignment.assigned;
142142
for( auto & chunk : sourceChunks )
143143
{
144-
chunk.mpi_rank = nextRank(); // @todo check negative
144+
chunk.mpi_rank = nextRank();
145145
sinkChunks.push_back( std::move( chunk ) );
146146
}
147147
return sinkChunks;
@@ -222,6 +222,14 @@ namespace chunk_assignment
222222

223223
namespace
224224
{
225+
/**
226+
* @brief Compute the intersection of two chunks.
227+
*
228+
* @param offset Offset of chunk 1, result will be written in place.
229+
* @param extent Extent of chunk 1, result will be written in place.
230+
* @param withinOffset Offset of chunk 2.
231+
* @param withinExtent Extent of chunk 2.
232+
*/
225233
void
226234
restrictToSelection(
227235
Offset & offset,
@@ -272,6 +280,17 @@ namespace chunk_assignment
272280
}
273281
};
274282

283+
/**
284+
* @brief Slice chunks to a maximum size and sort those by size.
285+
*
286+
* Chunks are sliced into hyperslabs along a specified dimension.
287+
* Returned chunks may be larger than the specified maximum size
288+
* if hyperslabs of thickness 1 are larger than that size.
289+
*
290+
* @param table Chunks of arbitrary sizes.
291+
* @param maxSize The maximum size that returned chunks should have.
292+
* @param dimension The dimension along which to create hyperslabs.
293+
*/
275294
std::vector< SizedChunk >
276295
splitToSizeSorted(
277296
ChunkTable const & table,
@@ -393,36 +412,65 @@ namespace chunk_assignment
393412
totalExtent += chunkExtent;
394413
}
395414
size_t const idealSize = totalExtent / sinkRanks.size();
415+
/*
416+
* Split chunks into subchunks of size at most idealSize.
417+
* The resulting list of chunks is sorted by chunk size in decreasing
418+
* order. This is important for the greedy Bin-Packing approximation
419+
* algorithm.
420+
* Under sub-ideal circumstances, chunks may not be splittable small
421+
* enough. This algorithm will still produce results just fine in that
422+
* case, but it will not keep the factor-2 approximation.
423+
*/
396424
std::vector< SizedChunk > digestibleChunks =
397425
splitToSizeSorted( sourceChunks, idealSize );
398426

427+
/*
428+
* Worker lambda: Iterate the reading processes once and greedily assign
429+
* the largest chunks to them without exceeding idealSize amount of
430+
* data per process.
431+
*/
399432
auto worker = [ &sinkRanks,
400433
&digestibleChunks,
401434
&sinkChunks,
402435
idealSize ]() {
403436
for( auto destRank : sinkRanks )
404437
{
438+
/*
439+
* Within the second call of the worker lambda, this will not
440+
* be true any longer, strictly speaking.
441+
* The trick of this algorithm is to pretend that it is.
442+
*/
405443
size_t leftoverSize = idealSize;
406444
{
407445
auto it = digestibleChunks.begin();
408446
while( it != digestibleChunks.end() )
409447
{
410448
if( it->dataSize >= idealSize )
411449
{
450+
/*
451+
* This branch is only taken if it was not possible
452+
* to slice chunks small enough -- or exactly the
453+
* right size.
454+
* In any case, the chunk will be the only one
455+
* assigned to the process within this call of the
456+
* worker lambda, so the loop can be broken out of.
457+
*/
412458
it->chunk.mpi_rank = destRank.first;
413459
sinkChunks.emplace_back( std::move( it->chunk ) );
414460
digestibleChunks.erase( it );
415461
break;
416462
}
417463
else if( it->dataSize <= leftoverSize )
418464
{
465+
// assign smaller chunks as long as they fit
419466
it->chunk.mpi_rank = destRank.first;
420467
sinkChunks.emplace_back( std::move( it->chunk ) );
421468
leftoverSize -= it->dataSize;
422469
it = digestibleChunks.erase( it );
423470
}
424471
else
425472
{
473+
// look for smaller chunks
426474
++it;
427475
}
428476
}
@@ -434,6 +482,14 @@ namespace chunk_assignment
434482
// of the bin packing problem
435483
worker();
436484
worker();
485+
/*
486+
* By the nature of the greedy approach, each iteration of the outer
487+
* for loop in the worker assigns chunks to the current rank that sum
488+
* up to at least more than half of the allowed idealSize. (Until it
489+
* runs out of chunks).
490+
* This means that calling the worker twice guarantees a full
491+
* distribution.
492+
*/
437493

438494
return sinkChunks;
439495
}

0 commit comments

Comments
 (0)