GALib-Parallel
Header-only C++ library for parallel evolutionary algorithms
Loading...
Searching...
No Matches
MpiCommunicator.h
Go to the documentation of this file.
1#ifndef MPI_COMMUNICATOR_H
2#define MPI_COMMUNICATOR_H
3
4#pragma once
5
8#include <mpi.h>
9#include <vector>
10#include <cstdint>
11
12namespace galib::internal {
13 template <typename GeneType>
14 class MpiCommunicator : public Communicator<GeneType> {
15 private:
16 static constexpr int MPI_MESSAGE_TAG = 99;
17
18 static constexpr std::size_t RECEIVE_BUFFER_OVERHEAD = 1024;
19
20 Serializer<GeneType>& serializer_m;
21 std::vector<uint8_t> receive_buffer_m;
22 MigrationBuffer<GeneType>* migration_buffer_m = nullptr;
23
24 MPI_Comm comm_m;
25 int rank_m;
26 std::size_t network_size_m;
27
28 bool is_running_m = false;
29 MPI_Request mpi_request_m = MPI_REQUEST_NULL;
30
31 public:
33 const std::size_t max_payload_size,
34 const MPI_Comm comm = MPI_COMM_WORLD)
35 : serializer_m(serializer), comm_m(comm) {
36 int initialized;
37 MPI_Initialized(&initialized);
38 if (!initialized) {
39 throw std::runtime_error("MPI must be initialized before creating MpiCommunicator");
40 }
41
42 receive_buffer_m.resize(max_payload_size + RECEIVE_BUFFER_OVERHEAD);
43
44 int rank;
45 int network_size;
46 MPI_Comm_rank(comm_m, &rank);
47 MPI_Comm_size(comm_m, &network_size);
48
49 rank_m = rank;
50 network_size_m = static_cast<std::size_t>(network_size);
51 }
52
56
57 void startReceiving(MigrationBuffer<GeneType>& target_buffer) override {
59
60 migration_buffer_m = &target_buffer;
61
62 MPI_Irecv(
63 receive_buffer_m.data(),
64 static_cast<int>(receive_buffer_m.size()),
65 MPI_BYTE,
66 MPI_ANY_SOURCE,
67 MPI_MESSAGE_TAG,
68 comm_m,
69 &mpi_request_m
70 );
71
72 is_running_m = true;
73 }
74
75 void update() override {
76 if (!is_running_m) return;
77
78 int is_received;
79
80 while (true) {
81 MPI_Status status;
82
83 MPI_Test(
84 &mpi_request_m,
85 &is_received,
86 &status
87 );
88
89 if (!is_received) break;
90
91 int received_bytes_count;
92
93 MPI_Get_count(
94 &status,
95 MPI_BYTE,
96 &received_bytes_count
97 );
98
99 std::vector<Individual<GeneType>> migrants = serializer_m.deserialize(
100 receive_buffer_m.data(),
101 static_cast<std::size_t>(received_bytes_count)
102 );
103
104 migration_buffer_m->push(std::move(migrants));
105
106 MPI_Irecv(
107 receive_buffer_m.data(),
108 static_cast<int>(receive_buffer_m.size()),
109 MPI_BYTE,
110 MPI_ANY_SOURCE,
111 MPI_MESSAGE_TAG,
112 comm_m,
113 &mpi_request_m
114 );
115 }
116 }
117
118 void stopReceiving() override {
119 if (!is_running_m) return;
120
121 is_running_m = false;
122
123 if (mpi_request_m != MPI_REQUEST_NULL) {
124 MPI_Cancel(&mpi_request_m);
125
126 MPI_Status status;
127 MPI_Wait(&mpi_request_m, &status);
128
129 mpi_request_m = MPI_REQUEST_NULL;
130 }
131 }
132
133 void send(const std::vector<Individual<GeneType>>& deme, const std::size_t destination_rank) override {
134 const std::vector<std::uint8_t> serialized_data = serializer_m.serialize(deme);
135
136 MPI_Send(
137 serialized_data.data(),
138 static_cast<int>(serialized_data.size()),
139 MPI_BYTE,
140 static_cast<int>(destination_rank),
141 MPI_MESSAGE_TAG,
142 comm_m
143 );
144 }
145
146 void broadcast(const std::vector<Individual<GeneType>>& deme,
147 const std::vector<std::size_t>& destination_ranks) override {
148 if (destination_ranks.empty()) return;
149
150 const std::vector<std::uint8_t> serialized_data = serializer_m.serialize(deme);
151
152 for (const auto& destination_rank : destination_ranks) {
153 MPI_Send(
154 serialized_data.data(),
155 static_cast<int>(serialized_data.size()),
156 MPI_BYTE,
157 static_cast<int>(destination_rank),
158 MPI_MESSAGE_TAG,
159 comm_m
160 );
161 }
162 }
163
165 struct DoubleInt {
166 double fitness;
167 int rank;
168 };
169
170 const DoubleInt local_best{individual.getFitness(), rank_m};
171 DoubleInt global_best;
172
173 MPI_Allreduce(
174 &local_best,
175 &global_best,
176 1,
177 MPI_DOUBLE_INT,
178 MPI_MINLOC,
179 comm_m
180 );
181
182 std::vector<std::uint8_t> buffer;
183 int buffer_size = 0;
184
185 if (rank_m == global_best.rank) {
186 buffer = std::move(serializer_m.serialize(individual));
187 buffer_size = static_cast<int>(buffer.size());
188 }
189
190 MPI_Bcast(
191 &buffer_size,
192 1,
193 MPI_INT,
194 global_best.rank,
195 comm_m
196 );
197
198 if (buffer_size > 0) {
199 if (rank_m != global_best.rank) {
200 buffer.resize(buffer_size);
201 }
202
203 MPI_Bcast(
204 buffer.data(),
205 buffer_size,
206 MPI_BYTE,
207 global_best.rank,
208 comm_m
209 );
210 }
211
212 if (rank_m == global_best.rank) { return individual; }
213
214 auto deserialized = serializer_m.deserialize(buffer);
215 if (deserialized.empty()) {
216 return std::move(individual);
217 }
218
219 return std::move(deserialized[0]);
220 }
221
222
223 [[nodiscard]] std::size_t getRank() const override {
224 return rank_m;
225 }
226
227 [[nodiscard]] std::size_t getSize() const override {
228 return network_size_m;
229 }
230 };
231}
232
233#endif // MPI_COMMUNICATOR_H
Represents a single candidate solution in the population.
Definition Individual.h:23
double getFitness() const
Gets the current fitness score.
Definition Individual.h:77
Interface for network communication between islands.
Interface for a thread-safe buffer that stores incoming migrants.
virtual void push(std::vector< Individual< GeneType > > &&incoming_deme_batch)=0
Adds a batch of individuals to the buffer.
MpiCommunicator(Serializer< GeneType > &serializer, const std::size_t max_payload_size, const MPI_Comm comm=MPI_COMM_WORLD)
Individual< GeneType > allReduceBest(Individual< GeneType > individual) const override
Global reduction to find the best individual across all islands.
void broadcast(const std::vector< Individual< GeneType > > &deme, const std::vector< std::size_t > &destination_ranks) override
Sends a deme to multiple destination islands.
std::size_t getRank() const override
Gets the network rank of the local node.
void update() override
Processes any pending network events (e.g. polls for incoming messages).
void send(const std::vector< Individual< GeneType > > &deme, const std::size_t destination_rank) override
Sends a deme to a specific destination island.
std::size_t getSize() const override
Gets the total number of islands in the network.
void startReceiving(MigrationBuffer< GeneType > &target_buffer) override
Begins asynchronous background receiving of migrants.
void stopReceiving() override
Stops background communication activities.
Interface for converting individuals to/from byte streams for network transport.
Definition Serializer.h:19
virtual std::vector< std::uint8_t > serialize(const std::vector< Individual< GeneType > > &individuals) const =0
Serializes multiple individuals into a byte vector.
virtual std::vector< Individual< GeneType > > deserialize(const std::vector< std::uint8_t > &individuals_serialized) const =0
Deserializes a byte vector into individuals.