Commit 290e85f7 authored by Peter W. Draper's avatar Peter W. Draper
Browse files

Merge branch 'repartition-updates' into 'master'

Minimize particle movement during repartitioning

Attempt to keep particles on local nodes by comparing the new and old regions and
permuting the solution by picking new regions with the largest number of 
cells in the old regions. Gives an unmoved fraction of around 42% for larger
numbers of ranks (compared to `1/nrank` for the current scheme).

Doesn't make a huge improvement to runtimes, seems to be around 10% on a
repartition step of `EAGLE_25`, and we only do that infrequently.


See merge request !288
parents 982a3664 b91a5ff3
...@@ -318,6 +318,23 @@ void engine_redistribute(struct engine *e) { ...@@ -318,6 +318,23 @@ void engine_redistribute(struct engine *e) {
MPI_COMM_WORLD) != MPI_SUCCESS) MPI_COMM_WORLD) != MPI_SUCCESS)
error("Failed to allreduce particle transfer counts."); error("Failed to allreduce particle transfer counts.");
/* Report how many particles will be moved. */
if (e->verbose) {
if (e->nodeID == 0) {
size_t total = 0;
size_t unmoved = 0;
for (int p = 0, r = 0; p < nr_nodes; p++) {
for (int s = 0; s < nr_nodes; s++) {
total += counts[r];
if (p == s) unmoved += counts[r];
r++;
}
}
message("%ld of %ld (%.2f%%) of particles moved", total - unmoved,
total, 100.0 * (double)(total - unmoved) / (double)total);
}
}
/* Get all the g_counts from all the nodes. */ /* Get all the g_counts from all the nodes. */
if (MPI_Allreduce(MPI_IN_PLACE, g_counts, nr_nodes * nr_nodes, MPI_INT, if (MPI_Allreduce(MPI_IN_PLACE, g_counts, nr_nodes * nr_nodes, MPI_INT,
MPI_SUM, MPI_COMM_WORLD) != MPI_SUCCESS) MPI_SUM, MPI_COMM_WORLD) != MPI_SUCCESS)
......
...@@ -278,6 +278,18 @@ static void split_metis(struct space *s, int nregions, int *celllist) { ...@@ -278,6 +278,18 @@ static void split_metis(struct space *s, int nregions, int *celllist) {
#endif #endif
#if defined(WITH_MPI) && defined(HAVE_METIS) #if defined(WITH_MPI) && defined(HAVE_METIS)
/* qsort support. */
struct indexval {
int index;
int count;
};
static int indexvalcmp(const void *p1, const void *p2) {
const struct indexval *iv1 = (const struct indexval *)p1;
const struct indexval *iv2 = (const struct indexval *)p2;
return iv2->count - iv1->count;
}
/** /**
* @brief Partition the given space into a number of connected regions. * @brief Partition the given space into a number of connected regions.
* *
...@@ -382,14 +394,70 @@ static void pick_metis(struct space *s, int nregions, int *vertexw, int *edgew, ...@@ -382,14 +394,70 @@ static void pick_metis(struct space *s, int nregions, int *vertexw, int *edgew,
if (regionid[k] < 0 || regionid[k] >= nregions) if (regionid[k] < 0 || regionid[k] >= nregions)
error("Got bad nodeID %" PRIDX " for cell %i.", regionid[k], k); error("Got bad nodeID %" PRIDX " for cell %i.", regionid[k], k);
/* We want a solution in which the current regions of the space are
* preserved when possible, to avoid unneccesary particle movement.
* So create a 2d-array of cells counts that are common to all pairs
* of old and new ranks. Each element of the array has a cell count and
* an unique index so we can sort into decreasing counts. */
int indmax = nregions * nregions;
struct indexval *ivs = malloc(sizeof(struct indexval) * indmax);
bzero(ivs, sizeof(struct indexval) * indmax);
for (int k = 0; k < ncells; k++) {
int index = regionid[k] + nregions * s->cells_top[k].nodeID;
ivs[index].count++;
ivs[index].index = index;
}
qsort(ivs, indmax, sizeof(struct indexval), indexvalcmp);
/* Go through the ivs using the largest counts first, these are the
* regions with the most cells in common, old partition to new. */
int *oldmap = malloc(sizeof(int) * nregions);
int *newmap = malloc(sizeof(int) * nregions);
for (int k = 0; k < nregions; k++) {
oldmap[k] = -1;
newmap[k] = -1;
}
for (int k = 0; k < indmax; k++) {
/* Stop when all regions with common cells have been considered. */
if (ivs[k].count == 0) break;
/* Store old and new IDs, if not already used. */
int oldregion = ivs[k].index / nregions;
int newregion = ivs[k].index - oldregion * nregions;
if (newmap[newregion] == -1 && oldmap[oldregion] == -1) {
newmap[newregion] = oldregion;
oldmap[oldregion] = newregion;
}
}
/* Handle any regions that did not get selected by picking an unused rank
* from oldmap and assigning to newmap. */
int spare = 0;
for (int k = 0; k < nregions; k++) {
if (newmap[k] == -1) {
for (int j = spare; j < nregions; j++) {
if (oldmap[j] == -1) {
newmap[k] = j;
oldmap[j] = j;
spare = j;
break;
}
}
}
}
/* Set the cell list to the region index. */ /* Set the cell list to the region index. */
for (int k = 0; k < ncells; k++) { for (int k = 0; k < ncells; k++) {
celllist[k] = regionid[k]; celllist[k] = newmap[regionid[k]];
} }
/* Clean up. */ /* Clean up. */
if (weights_v != NULL) free(weights_v); if (weights_v != NULL) free(weights_v);
if (weights_e != NULL) free(weights_e); if (weights_e != NULL) free(weights_e);
free(ivs);
free(oldmap);
free(newmap);
free(xadj); free(xadj);
free(adjncy); free(adjncy);
free(regionid); free(regionid);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment