Skip to content

Commit e7a6f57

Browse files
committed
Add distribution strategies to ChunkInfo files
1 parent 885d1ba commit e7a6f57

2 files changed

Lines changed: 711 additions & 1 deletion

File tree

include/openPMD/ChunkInfo.hpp

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121
#pragma once
2222

23+
#include "openPMD/benchmark/mpi/BlockSlicer.hpp"
2324
#include "openPMD/Dataset.hpp" // Offset, Extent
2425

2526
#include <vector>
@@ -76,4 +77,233 @@ struct WrittenChunkInfo : ChunkInfo
7677
};
7778

7879
using ChunkTable = std::vector< WrittenChunkInfo >;
80+
81+
namespace chunk_assignment
82+
{
83+
constexpr char const * HOSTFILE_VARNAME = "MPI_WRITTEN_HOSTFILE";
84+
85+
using RankMeta = std::map< unsigned int, std::string >;
86+
87+
struct PartialAssignment
88+
{
89+
ChunkTable notAssigned;
90+
ChunkTable assigned;
91+
92+
explicit PartialAssignment() = default;
93+
PartialAssignment( ChunkTable notAssigned );
94+
PartialAssignment( ChunkTable notAssigned, ChunkTable assigned );
95+
};
96+
97+
/**
98+
* @brief Interface for a chunk distribution strategy.
99+
*
100+
* Used for implementing algorithms that read a ChunkTable as produced
101+
* by BaseRecordComponent::availableChunks() and produce as result a
102+
* ChunkTable that guides data sinks on how to load data into reading
103+
* processes.
104+
*/
105+
struct Strategy
106+
{
107+
/**
108+
* @brief Assign chunks to be loaded to reading processes.
109+
*
110+
* @param partialAssignment Two chunktables, one of unassigned chunks
111+
* and one of chunks that might have already been assigned
112+
* previously.
113+
* Merge the unassigned chunks into the partially assigned table.
114+
* @param in Meta information on writing processes, e.g. hostnames.
115+
* @param out Meta information on reading processes, e.g. hostnames.
116+
* @return ChunkTable A table that assigns chunks to reading processes.
117+
*/
118+
virtual ChunkTable
119+
assign(
120+
PartialAssignment partialAssignment,
121+
RankMeta const & in,
122+
RankMeta const & out ) = 0;
123+
124+
virtual ~Strategy() = default;
125+
};
126+
127+
/**
128+
* @brief A chunk distribution strategy that guarantees no complete
129+
* distribution.
130+
*
131+
* Combine with a full Strategy using the FromPartialStrategy struct to
132+
* obtain a Strategy that works in two phases:
133+
* 1. Apply the partial strategy.
134+
* 2. Apply the full strategy to assign unassigned leftovers.
135+
*
136+
*/
137+
struct PartialStrategy
138+
{
139+
/**
140+
* @brief Assign chunks to be loaded to reading processes.
141+
*
142+
* @param partialAssignment Two chunktables, one of unassigned chunks
143+
* and one of chunks that might have already been assigned
144+
* previously.
145+
* Merge the unassigned chunks into the partially assigned table.
146+
* @param in Meta information on writing processes, e.g. hostnames.
147+
* @param out Meta information on reading processes, e.g. hostnames.
148+
* @return PartialAssignment Two chunktables, one of leftover chunks
149+
* that were not assigned and one that assigns chunks to
150+
* reading processes.
151+
*/
152+
virtual PartialAssignment
153+
assign(
154+
PartialAssignment partialAssignment,
155+
RankMeta const & in,
156+
RankMeta const & out ) = 0;
157+
158+
virtual ~PartialStrategy() = default;
159+
};
160+
161+
ChunkTable
162+
assignChunks(
163+
ChunkTable,
164+
RankMeta const & rankMetaIn,
165+
RankMeta const & rankMetaOut,
166+
Strategy & strategy );
167+
168+
PartialAssignment assignChunks(
169+
ChunkTable,
170+
RankMeta const & rankMetaIn,
171+
RankMeta const & rankMetaOut,
172+
PartialStrategy & strategy );
173+
174+
/**
175+
* @brief Combine a PartialStrategy and a Strategy to obtain a Strategy
176+
* working in two phases.
177+
*
178+
* 1. Apply the PartialStrategy to obtain a PartialAssignment.
179+
* This may be a heuristic that will not work under all circumstances,
180+
* e.g. trying to distribute chunks within the same compute node.
181+
* 2. Apply the Strategy to assign leftovers.
182+
* This guarantees correctness in case the heuristics in the first phase
183+
* were not applicable e.g. due to a suboptimal setup.
184+
*
185+
*/
186+
struct FromPartialStrategy : Strategy
187+
{
188+
FromPartialStrategy(
189+
std::unique_ptr< PartialStrategy > firstPass,
190+
std::unique_ptr< Strategy > secondPass );
191+
192+
virtual ChunkTable
193+
assign( PartialAssignment, RankMeta const & in, RankMeta const & out );
194+
195+
private:
196+
std::unique_ptr< PartialStrategy > m_firstPass;
197+
std::unique_ptr< Strategy > m_secondPass;
198+
};
199+
200+
/**
201+
* @brief Simple strategy that assigns produced chunks to reading processes
202+
* in a round-Robin manner.
203+
*
204+
*/
205+
struct RoundRobin : Strategy
206+
{
207+
ChunkTable
208+
assign( PartialAssignment, RankMeta const & in, RankMeta const & out );
209+
};
210+
211+
/**
212+
* @brief Strategy that assigns chunks to be read by processes within
213+
* the same host that produced the chunk.
214+
*
215+
* The distribution strategy within one such chunk can be flexibly
216+
* chosen.
217+
*
218+
*/
219+
struct ByHostname : PartialStrategy
220+
{
221+
ByHostname( std::unique_ptr< Strategy > withinNode );
222+
223+
PartialAssignment
224+
assign( PartialAssignment, RankMeta const & in, RankMeta const & out )
225+
override;
226+
227+
private:
228+
std::unique_ptr< Strategy > m_withinNode;
229+
};
230+
231+
/**
232+
* @brief Slice the n-dimensional dataset into hyperslabs and distribute
233+
* chunks according to them.
234+
*
235+
* This strategy only produces chunks in the returned ChunkTable for the
236+
* calling parallel process.
237+
* Incoming chunks are intersected with the hyperslab and assigned to the
238+
* current parallel process in case this intersection is non-empty.
239+
*
240+
*/
241+
struct ByCuboidSlice : Strategy
242+
{
243+
ByCuboidSlice(
244+
std::unique_ptr< BlockSlicer > blockSlicer,
245+
Extent totalExtent,
246+
unsigned int mpi_rank,
247+
unsigned int mpi_size );
248+
249+
ChunkTable
250+
assign( PartialAssignment, RankMeta const & in, RankMeta const & out )
251+
override;
252+
253+
private:
254+
std::unique_ptr< BlockSlicer > blockSlicer;
255+
Extent totalExtent;
256+
unsigned int mpi_rank, mpi_size;
257+
};
258+
259+
/**
260+
* @brief Strategy that tries to assign chunks in a balanced manner without
261+
* arbitrarily cutting chunks.
262+
*
263+
* Idea:
264+
* Calculate the ideal amount of data to be loaded per parallel process
265+
* and cut chunks s.t. no chunk is larger than that ideal size.
266+
* The resulting problem is an instance of the Bin-Packing problem which
267+
* can be solved by a factor-2 approximation, meaning that a reading process
268+
* will be assigned at worst twice the ideal amount of data.
269+
*
270+
*/
271+
struct BinPacking : Strategy
272+
{
273+
size_t splitAlongDimension = 0;
274+
275+
/**
276+
* @param splitAlongDimension If a chunk needs to be split, split it
277+
* along this dimension.
278+
*/
279+
BinPacking( size_t splitAlongDimension = 0 );
280+
281+
ChunkTable
282+
assign( PartialAssignment, RankMeta const & in, RankMeta const & out )
283+
override;
284+
};
285+
286+
/**
287+
* C++11 doesn't have it and it's useful for some of these.
288+
*/
289+
template< typename T, typename... Args >
290+
std::unique_ptr< T >
291+
make_unique( Args &&... args )
292+
{
293+
return std::unique_ptr< T >( new T( std::forward< Args >( args )... ) );
294+
}
295+
} // namespace chunk_assignment
296+
297+
namespace host_info
298+
{
299+
enum class Method
300+
{
301+
HOSTNAME
302+
};
303+
304+
std::string byMethod( Method );
305+
306+
std::string
307+
hostname();
308+
} // namespace host_info
79309
} // namespace openPMD

0 commit comments

Comments
 (0)