Skip to content

Commit

Permalink
Documentazione
Browse files Browse the repository at this point in the history
  • Loading branch information
franzpoeschel committed Feb 23, 2021
1 parent 8d3ea08 commit 342f898
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 2 deletions.
91 changes: 90 additions & 1 deletion include/openPMD/ChunkInfo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,19 +94,61 @@ namespace chunk_assignment
PartialAssignment( ChunkTable notAssigned, ChunkTable assigned );
};

/**
* @brief Interface for a chunk distribution strategy.
*
* Used for implementing algorithms that read a ChunkTable as produced
* by BaseRecordComponent::availableChunks() and produce as result a
* ChunkTable that guides data sinks on how to load data into reading
* processes.
*/
struct Strategy
{
/**
* @brief Assign chunks to be loaded to reading processes.
*
* @param partialAssignment Two chunktables, one of unassigned chunks
* and one of chunks that might have already been assigned
* previously.
* Merge the unassigned chunks into the partially assigned table.
* @param in Meta information on writing processes, e.g. hostnames.
* @param out Meta information on reading processes, e.g. hostnames.
* @return ChunkTable A table that assigns chunks to reading processes.
*/
virtual ChunkTable
assign(
PartialAssignment,
PartialAssignment partialAssignment,
RankMeta const & in,
RankMeta const & out ) = 0;

virtual ~Strategy() = default;
};

/**
* @brief A chunk distribution strategy that guarantees no complete
* distribution.
*
* Combine with a full Strategy using the FromPartialStrategy struct to
* obtain a Strategy that works in two phases:
* 1. Apply the partial strategy.
* 2. Apply the full strategy to assign unassigned leftovers.
*
*/
struct PartialStrategy
{
/**
* @brief Assign chunks to be loaded to reading processes.
*
* @param partialAssignment Two chunktables, one of unassigned chunks
* and one of chunks that might have already been assigned
* previously.
* Merge the unassigned chunks into the partially assigned table.
* @param in Meta information on writing processes, e.g. hostnames.
* @param out Meta information on reading processes, e.g. hostnames.
* @return PartialAssignment Two chunktables, one of leftover chunks
* that were not assigned and one that assigns chunks to
* reading processes.
*/
virtual PartialAssignment
assign(
PartialAssignment,
Expand All @@ -123,6 +165,18 @@ namespace chunk_assignment
RankMeta const & rankMetaOut,
Strategy & strategy );

/**
* @brief Combine a PartialStrategy and a Strategy to obtain a Strategy
* working in two phases.
*
* 1. Apply the PartialStrategy to obtain a PartialAssignment.
* This may be a heuristic that will not work under all circumstances,
* e.g. trying to distribute chunks within the same compute node.
* 2. Apply the Strategy to assign leftovers.
* This guarantees correctness in case the heuristics in the first phase
* were not applicable e.g. due to a suboptimal setup.
*
*/
struct FromPartialStrategy : Strategy
{
FromPartialStrategy(
Expand All @@ -137,12 +191,25 @@ namespace chunk_assignment
std::unique_ptr< Strategy > m_secondPass;
};

/**
* @brief Simple strategy that assigns produced chunks to reading processes
* in a round-Robin manner.
*
*/
struct RoundRobin : Strategy
{
ChunkTable
assign( PartialAssignment, RankMeta const & in, RankMeta const & out );
};

/**
* @brief Strategy that assigns chunks to be read by processes within
* the same host that produced the chunk.
*
* The distribution strategy within one such chunk can be flexibly
* chosen.
*
*/
struct ByHostname : PartialStrategy
{
ByHostname( std::unique_ptr< Strategy > withinNode );
Expand All @@ -155,6 +222,16 @@ namespace chunk_assignment
std::unique_ptr< Strategy > m_withinNode;
};

/**
* @brief Slice the n-dimensional dataset into hyperslabs and distribute
* chunks according to them.
*
* This strategy only produces chunks in the returned ChunkTable for the
* calling parallel process.
* Incoming chunks are intersected with the hyperslab and assigned to the
* current parallel process in case this intersection is non-empty.
*
*/
struct ByCuboidSlice : Strategy
{
ByCuboidSlice(
Expand All @@ -173,6 +250,18 @@ namespace chunk_assignment
unsigned int mpi_rank, mpi_size;
};

/**
* @brief Strategy that tries to assign chunks in a balanced manner without
* arbitrarily cutting chunks.
*
* Idea:
* Calculate the ideal amount of data to be loaded per parallel process
* and cut chunks s.t. no chunk is larger than that ideal size.
* The resulting problem is an instance of the Bin-Packing problem which
* can be solved by a factor-2 approximation, meaning that a reading process
* will be assigned at worst twice the ideal amount of data.
*
*/
struct BinPacking : Strategy
{
ChunkTable
Expand Down
58 changes: 57 additions & 1 deletion src/ChunkInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ namespace chunk_assignment
ChunkTable & sinkChunks = partialAssignment.assigned;
for( auto & chunk : sourceChunks )
{
chunk.sourceID = nextRank(); // @todo check negative
chunk.sourceID = nextRank();
sinkChunks.push_back( std::move( chunk ) );
}
return sinkChunks;
Expand Down Expand Up @@ -225,6 +225,14 @@ namespace chunk_assignment

namespace
{
/**
* @brief Compute the intersection of two chunks.
*
* @param offset Offset of chunk 1, result will be written in place.
* @param extent Extent of chunk 1, result will be written in place.
* @param withinOffset Offset of chunk 2.
* @param withinExtent Extent of chunk 2.
*/
void
restrictToSelection(
Offset & offset,
Expand Down Expand Up @@ -275,6 +283,17 @@ namespace chunk_assignment
}
};

/**
* @brief Slice chunks to a maximum size and sort those by size.
*
* Chunks are sliced into hyperslabs along a specified dimension.
* Returned chunks may be larger than the specified maximum size
* if hyperslabs of thickness 1 are larger than that size.
*
* @param table Chunks of arbitrary sizes.
* @param maxSize The maximum size that returned chunks should have.
* @param dimension The dimension along which to create hyperslabs.
*/
std::vector< SizedChunk >
splitToSizeSorted(
ChunkTable const & table,
Expand Down Expand Up @@ -396,36 +415,65 @@ namespace chunk_assignment
totalExtent += chunkExtent;
}
size_t const idealSize = totalExtent / sinkRanks.size();
/*
* Split chunks into subchunks of size at most idealSize.
* The resulting list of chunks is sorted by chunk size in decreasing
* order. This is important for the greedy Bin-Packing approximation
* algorithm.
* Under sub-ideal circumstances, chunks may not be splittable small
* enough. This algorithm will still produce results just fine in that
* case, but it will not keep the factor-2 approximation.
*/
std::vector< SizedChunk > digestibleChunks =
splitToSizeSorted( sourceChunks, idealSize );

/*
* Worker lambda: Iterate the reading processes once and greedily assign
* the largest chunks to them without exceeding idealSize amount of
* data per process.
*/
auto worker = [ &sinkRanks,
&digestibleChunks,
&sinkChunks,
idealSize ]() {
for( auto destRank : sinkRanks )
{
/*
* Within the second call of the worker lambda, this will not
* be true any longer, strictly speaking.
* The trick of this algorithm is to pretend that it is.
*/
size_t leftoverSize = idealSize;
{
auto it = digestibleChunks.begin();
while( it != digestibleChunks.end() )
{
if( it->dataSize >= idealSize )
{
/*
* This branch is only taken if it was not possible
* to slice chunks small enough -- or exactly the
* right size.
* In any case, the chunk will be the only one
* assigned to the process within this call of the
* worker lambda, so the loop can be broken out of.
*/
it->chunk.sourceID = destRank.first;
sinkChunks.emplace_back( std::move( it->chunk ) );
digestibleChunks.erase( it );
break;
}
else if( it->dataSize <= leftoverSize )
{
// assign smaller chunks as long as they fit
it->chunk.sourceID = destRank.first;
sinkChunks.emplace_back( std::move( it->chunk ) );
leftoverSize -= it->dataSize;
it = digestibleChunks.erase( it );
}
else
{
// look for smaller chunks
++it;
}
}
Expand All @@ -437,6 +485,14 @@ namespace chunk_assignment
// of the bin packing problem
worker();
worker();
/*
* By the nature of the greedy approach, each iteration of the outer
* for loop in the worker assigns chunks to the current rank that sum
* up to at least more than half of the allowed idealSize. (Until it
* runs out of chunks).
* This means that calling the worker twice guarantees a full
* distribution.
*/

return sinkChunks;
}
Expand Down

0 comments on commit 342f898

Please sign in to comment.