GCC Code Coverage Report


Directory: src/
File: src/mngo/mngo_drom.c
Date: 2026-09-15 07:37:49
Exec Total Coverage
Lines: 29 29 100.0%
Functions: 3 3 100.0%
Branches: 2 2 100.0%

Line Branch Exec Source
1 /*********************************************************************************/
2 /* Copyright 2009-2026 Barcelona Supercomputing Center */
3 /* */
4 /* This file is part of the DLB library. */
5 /* */
6 /* DLB is free software: you can redistribute it and/or modify */
7 /* it under the terms of the GNU Lesser General Public License as published by */
8 /* the Free Software Foundation, either version 3 of the License, or */
9 /* (at your option) any later version. */
10 /* */
11 /* DLB is distributed in the hope that it will be useful, */
12 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 /* GNU Lesser General Public License for more details. */
15 /* */
16 /* You should have received a copy of the GNU Lesser General Public License */
17 /* along with DLB. If not, see <https://www.gnu.org/licenses/>. */
18 /*********************************************************************************/
19
20 #include "mngo/mngo_drom.h"
21
22 #include "LB_comm/shmem_procinfo.h"
23 #include "dlb_errors.h"
24 #include "mngo/mngo.h"
25 #include "mngo/mngo_balancer.h"
26
27 #include "LB_comm/shmem_mngo.h"
28 #include "support/mask_utils.h"
29 #include <math.h>
30
31 /*
32 * To decide how many cores DROM has to change we will solve for the number of
33 * processors that have to be removed from one process with *self* parallel
34 * efficiency to achieve *others* parallel efficiency, being this lower or
35 * greater than *self*.
36 */
37 10 static int balance(const subprocess_descriptor_t *spd, float self,
38 float others) {
39
40 10 mngo_info_t *mngo_info = spd->mngo_info;
41 cpu_set_t mask;
42 10 shmem_procinfo__getprocessmask(shmem_mngo_get_pid(mngo_info->mid), &mask,
43 DLB_DROM_FLAGS_NONE);
44
45 /*
46 * TODO: Implement also the cross-correlation of shared memory parallel
47 * efficiencies among different processes, to determine if reducing the
48 * number of active cores in the node (or activating EAR, in the future)
49 * will be useful or not.
50 *
51 * For now will activate DROM and reduce the amount of cores of the affected
52 * node with the hope that the load imbalance is persistent among nodes and
53 * DROM will be useful. And roll the decision back if performance in any
54 * node gets worse on the next observation.
55 */
56
57 /*
58 * +--------------------------+---------------+
59 * (ex. 1) SELF PE | GOOD | BAD |
60 * +--------------------------+---------------+
61 * +------------------------------------+-----+
62 * (ex. 2) SELF PE | GOOD | BAD |
63 * +------------------------------------+-----+
64 * +--------------------------------+---------+
65 * AVG. NODE PE | GOOD | BAD |
66 * +--------------------------------+---------+
67 *
68 * With the ratio operation we convert it to that:
69 *
70 * +--------------------------+-----+
71 * (ex. 1) SELF PE | GOOD | BAD |
72 * +--------------------------+-----+
73 * +--------------------------------+---+
74 * (ex. 2) SELF PE | GOOD | E |
75 * +--------------------------------+---+
76 */
77 10 float ratio_mpi_parallel_efficiency = ((float)self) / others;
78
79 /*
80 * PE = (GOOD + EXTRA) / (GOOD + BAD)
81 *
82 * To reduce the load balance we need make the elapsed time of the GOOD part
83 * take the same amount of time for all processes.
84 *
85 * Assuming that the aggregated CPU time is constant no matter how many
86 * cores a process has (i.e. ideal thread scalability). That is, the area
87 * described by the GOOD elapsed time and the number of cores (N) is
88 * constant for all values of N.
89 *
90 * +----+
91 * | |
92 * +----------+ | |
93 * N = 2 | | has the same area as N = 4 | |
94 * +----------+ | |
95 * GOOD = 12 +----+
96 * GOOD = 6
97 *
98 * This is obviously an hover-optimistic assumption and it will hold true
99 * depending on how good the application scales in OpenMP. But will
100 * generally hold true for scaling down the number of processes.
101 *
102 * TODO: When we have OpenMP metrics we can scale the number of processes to
103 * change taking into account the OpenMP parallel efficiency.
104 *
105 * This area is described by the product of the number of processes and the
106 * GOOD part. And we want to change the GOOD elapsed time to force the value
107 * to take the same as the average GOOD elapsed. From this objective we
108 * obtain the following equation:
109 *
110 * N * ( GOOD + DIFFERENCE ) = (N + dN) * GOOD
111 *
112 * Where dN is the change in number of cores. When dN is isolated we
113 * obtain:
114 *
115 * N * GOOD + dN * GOOD = N * ( GOOD + DIFFERENCE )
116 *
117 * dN * GOOD = N * ( GOOD + DIFFERENCE ) - N * GOOD
118 *
119 * ( GOOD + DIFFERENCE )
120 * dN = N * --------------------- - N = N * PE - N
121 * GOOD
122 *
123 * Due to that these operations are continuous but the dN has to be a
124 * discrete value. We ceil the multiplication to not release a little bit
125 * more cores than needed.
126 */
127 10 int core_count = mu_count_cores_intersecting_with_cpuset(&mask);
128 10 int core_change =
129 10 (int)ceil((float)core_count * ratio_mpi_parallel_efficiency) -
130 core_count;
131
132 /*
133 * Keep at least one core.
134 */
135 10 int maximum_core_release = -core_count + 1;
136 10 int bounded_core_change = max_int(core_change, maximum_core_release);
137
138 10 return bounded_core_change;
139 }
140
141 10 static void print_redistribution_report(const subprocess_descriptor_t *spd,
142 int self_cpu_delta) {
143 10 mngo_info_t *mngo_info = spd->mngo_info;
144
145 10 const char *region_name = "";
146 10 mngo_regions_manager_t *region_manager = &mngo_info->region_handler;
147 dlb_mngo_region_t **current_region;
148
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 4 times.
10 if (queue__peek_head(region_manager->active_regions,
149 (void **)&current_region) == DLB_SUCCESS) {
150 6 region_name = (*current_region)->name;
151 }
152 10 shmem_mngo_drom__print_redistribution(mngo_info->mid, self_cpu_delta,
153 region_name);
154 10 }
155
156 /*
157 * Once each process has used its history of performance to decide what actions
158 * to take, we put all the actions from all processes together to decide which
159 * actions have preference over which other actions.
160 *
161 * To do that we will use MPI to execute an all to all communication and then
162 * apply the same logic to all the collected data to leave all the mngo
163 * decisions in a consistant state.
164 */
165 10 void mngo_drom__balance(const subprocess_descriptor_t *spd, float self_pe,
166 float node_pe, cpu_set_t *new_cpu_mask) {
167
168 10 mngo_info_t *mngo_info = spd->mngo_info;
169
170 // 1. Decide how many CPU i would like to give/take
171 10 int self_cpu_delta = balance(spd, self_pe, node_pe);
172
173 // 2. Communicate these to other processes
174 int num_cpu_deltas =
175 10 shmem_mngo_drom__alltoall_deltas_start(mngo_info->mid, self_cpu_delta);
176
177 10 int *cpu_deltas = calloc(num_cpu_deltas, sizeof(int));
178 10 shmem_mngo_drom__alltoall_deltas_finish(mngo_info->mid, cpu_deltas);
179
180 // 3. Update how many CPU to give/take based on availability
181 int coherent_self_cpu_delta =
182 10 mngo_balancer(mngo_info->mid, cpu_deltas, num_cpu_deltas);
183 10 free(cpu_deltas);
184
185 // 4. Exchange CPUs
186 10 shmem_mngo_drom__redistribute(mngo_info->mid, coherent_self_cpu_delta,
187 new_cpu_mask);
188
189 // 5 Show CPU changes
190 10 print_redistribution_report(spd, coherent_self_cpu_delta);
191 10 }
192