GCC Code Coverage Report


Directory: ./
File: tasks/kapanova_s_image_smoothing/mpi/src/ops_mpi.cpp
Date: 2026-02-02 01:14:38
Exec Total Coverage
Lines: 185 202 91.6%
Functions: 21 26 80.8%
Branches: 63 106 59.4%

Line Branch Exec Source
1 #include "kapanova_s_image_smoothing/mpi/include/ops_mpi.hpp"
2
3 #include <mpi.h>
4
5 #include <algorithm>
6 #include <array>
7 #include <cmath>
8 #include <cstddef>
9 #include <cstdint>
10 #include <vector>
11
12 #include "kapanova_s_image_smoothing/common/include/common.hpp"
13
14 namespace kapanova_s_image_smoothing {
15
16 namespace {
17 constexpr int kTagExit = 0;
18 constexpr int kTagData = 2;
19 constexpr int kTagResult = 3;
20 constexpr int kRadius = 1;
21 constexpr int kKernelSize = (2 * kRadius) + 1;
22 constexpr float kSigma = 1.5F;
23 constexpr float kSigmaSquared = kSigma * kSigma;
24 constexpr int kEscapeSignal = 0;
25 constexpr int kNoEscapeSignal = 1;
26 } // namespace
27
28
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 KapanovaSImageSmoothingMPI::KapanovaSImageSmoothingMPI(const InType &in) : radius_(kRadius) {
29 SetTypeOfTask(GetStaticTypeOfTask());
30
3/8
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
6 GetInput() = in.empty() ? InType() : in;
31 6 }
32
33 6 bool KapanovaSImageSmoothingMPI::ValidationImpl() {
34 6 int rank = 0;
35 6 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
36
37
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (rank == 0) {
38 const auto &input_data = GetInput();
39
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 return !input_data.empty() && !input_data[0].empty();
40 }
41
42 return true;
43 }
44
45 6 bool KapanovaSImageSmoothingMPI::PreProcessingImpl() {
46 6 int rank = 0;
47 6 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
48
49
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (rank == 0) {
50 const auto &input_data = GetInput();
51
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (input_data.empty() || input_data[0].size() < 4) {
52 return false;
53 }
54
55 const auto &data = input_data[0];
56 3 width_ = (data[1] << 8) | data[0];
57 3 height_ = (data[3] << 8) | data[2];
58
59 3 const auto width_u = static_cast<size_t>(width_);
60 3 const auto height_u = static_cast<size_t>(height_);
61 3 const size_t required_pixels = width_u * height_u * 3U;
62 3 const size_t total_required_size = 4U + required_pixels;
63
64
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (data.size() < total_required_size) {
65 return false;
66 }
67
68 3 input_.assign(data.begin() + 4, data.end());
69
70 3 std::array<int, 2> dimensions = {width_, height_};
71 3 MPI_Bcast(dimensions.data(), 2, MPI_INT, 0, MPI_COMM_WORLD);
72
73 3 int image_size = static_cast<int>(input_.size());
74 3 MPI_Bcast(&image_size, 1, MPI_INT, 0, MPI_COMM_WORLD);
75
76
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (image_size > 0) {
77 3 MPI_Bcast(input_.data(), image_size, MPI_UNSIGNED_CHAR, 0, MPI_COMM_WORLD);
78 }
79
80 } else {
81 3 std::array<int, 2> dimensions = {0, 0};
82 3 MPI_Bcast(dimensions.data(), 2, MPI_INT, 0, MPI_COMM_WORLD);
83 3 width_ = dimensions[0];
84 3 height_ = dimensions[1];
85
86 3 int image_size = 0;
87 3 MPI_Bcast(&image_size, 1, MPI_INT, 0, MPI_COMM_WORLD);
88
89
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (image_size > 0) {
90 3 input_.resize(static_cast<size_t>(image_size));
91 3 MPI_Bcast(input_.data(), image_size, MPI_UNSIGNED_CHAR, 0, MPI_COMM_WORLD);
92 }
93 }
94
95 6 const auto width_u = static_cast<size_t>(width_);
96 6 const auto height_u = static_cast<size_t>(height_);
97 6 const size_t required_pixels = width_u * height_u * 3U;
98 6 result_.resize(required_pixels);
99 6 kernel_ = CreateKernel();
100
101 6 return true;
102 }
103
104 6 std::vector<float> KapanovaSImageSmoothingMPI::CreateKernel() {
105 const auto kernel_size = static_cast<size_t>(kKernelSize) * static_cast<size_t>(kKernelSize);
106 6 std::vector<float> kernel(kernel_size, 0.0F);
107 float norm = 0.0F;
108
109
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 6 times.
24 for (int i = -kRadius; i <= kRadius; ++i) {
110
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 18 times.
72 for (int j = -kRadius; j <= kRadius; ++j) {
111 54 const int index = ((i + kRadius) * kKernelSize) + (j + kRadius);
112 54 kernel[static_cast<size_t>(index)] = std::exp(-static_cast<float>((i * i) + (j * j)) / (2.0F * kSigmaSquared));
113 54 norm += kernel[static_cast<size_t>(index)];
114 }
115 }
116
117
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (norm > 0.0F) {
118
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 6 times.
60 for (auto &value : kernel) {
119 54 value /= norm;
120 }
121 }
122
123 6 return kernel;
124 }
125
126 3 void KapanovaSImageSmoothingMPI::ProcessBorderRows() {
127
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 3 times.
12 for (int x_coord = 0; x_coord < width_; ++x_coord) {
128 9 const auto top_pos = static_cast<size_t>(x_coord) * 3U;
129 9 SmoothPixel(&result_[top_pos], x_coord, 0);
130
131 9 const auto bottom_pos = static_cast<size_t>((height_ - 1) * width_ * 3) + (static_cast<size_t>(x_coord) * 3U);
132 9 SmoothPixel(&result_[bottom_pos], x_coord, height_ - 1);
133 }
134 3 }
135
136 void KapanovaSImageSmoothingMPI::ProcessRowRange(int start_row, int num_rows) {
137 const int end_row = std::min(start_row + num_rows, height_ - 1);
138
139 for (int y_coord = start_row; y_coord < end_row; ++y_coord) {
140 for (int x_coord = 0; x_coord < width_; ++x_coord) {
141 const auto pos = static_cast<size_t>(y_coord * width_ * 3) + (static_cast<size_t>(x_coord) * 3U);
142 SmoothPixel(&result_[pos], x_coord, y_coord);
143 }
144 }
145 }
146
147 3 void KapanovaSImageSmoothingMPI::SendImageData(int worker_rank, int row) {
148 const int data_size = CalculateDataSize(row);
149 const int start_pos = CalculateStartPosition(row);
150
151
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (data_size > 0) {
152 3 MPI_Send(&input_[static_cast<size_t>(start_pos)], data_size, MPI_UNSIGNED_CHAR, worker_rank, kTagData,
153 MPI_COMM_WORLD);
154 }
155 3 }
156
157 int KapanovaSImageSmoothingMPI::CalculateDataSize(int row) const {
158
2/8
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 if (row <= 0 || row >= height_ - 1) {
159 return 0;
160 }
161
162 3 return 3 * width_ * 3;
163 }
164
165 int KapanovaSImageSmoothingMPI::CalculateStartPosition(int row) const {
166
1/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 if (row <= 0 || row >= height_ - 1) {
167 return 0;
168 }
169
170 3 return (row - 1) * width_ * 3;
171 }
172
173 3 void KapanovaSImageSmoothingMPI::MasterProcess() {
174 3 const int satellites = GetCommSize() - 1;
175
176 3 ProcessBorderRows();
177
178 3 DistributeRowsToWorkers(satellites);
179
180 3 SendExitSignalToWorkers(satellites);
181 3 }
182
183 3 void KapanovaSImageSmoothingMPI::DistributeRowsToWorkers(int num_workers) {
184 int current_row = 1;
185
186
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 while (current_row < height_ - 1) {
187 3 const int processes_to_use = std::min(num_workers, height_ - 1 - current_row);
188
189 3 AssignRowsToWorkers(current_row, processes_to_use);
190 3 ReceiveResultsFromWorkers(current_row, processes_to_use);
191 3 current_row += processes_to_use;
192 }
193 3 }
194
195 3 void KapanovaSImageSmoothingMPI::AssignRowsToWorkers(int start_row, int num_workers) {
196
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 for (int i = 0; i < num_workers; ++i) {
197 3 const int worker_rank = i + 1;
198 3 const int row_to_process = start_row + i;
199
200
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (row_to_process <= 0 || row_to_process >= height_ - 1) {
201 continue;
202 }
203
204 3 std::array<int, 2> work_info = {kNoEscapeSignal, row_to_process};
205 3 MPI_Send(work_info.data(), 2, MPI_INT, worker_rank, kTagExit, MPI_COMM_WORLD);
206
207 3 SendImageData(worker_rank, row_to_process);
208 }
209 3 }
210
211 3 void KapanovaSImageSmoothingMPI::ReceiveResultsFromWorkers(int start_row, int num_workers) {
212
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 for (int i = 0; i < num_workers; ++i) {
213 3 const int worker_rank = i + 1;
214 3 const int result_row = start_row + i;
215
216
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (result_row >= 1 && result_row < height_ - 1) {
217 3 const auto result_pos = static_cast<size_t>(result_row) * static_cast<size_t>(width_) * 3;
218 3 MPI_Recv(&result_[result_pos], width_ * 3, MPI_UNSIGNED_CHAR, worker_rank, kTagResult, MPI_COMM_WORLD,
219 MPI_STATUS_IGNORE);
220 }
221 }
222 3 }
223
224 3 void KapanovaSImageSmoothingMPI::SendExitSignalToWorkers(int num_workers) {
225
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 for (int i = 1; i <= num_workers; ++i) {
226 3 std::array<int, 2> exit_info = {kEscapeSignal, 0};
227 3 MPI_Send(exit_info.data(), 2, MPI_INT, i, kTagExit, MPI_COMM_WORLD);
228 }
229 3 }
230
231 3 void KapanovaSImageSmoothingMPI::WorkerProcess() {
232 3 int local_width = width_;
233
234 3 std::vector<uint8_t> local_input;
235
1/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
3 std::vector<uint8_t> local_result(static_cast<size_t>(local_width * 3));
236
237 3 std::array<int, 2> work_info = {0, 0};
238
239 while (true) {
240
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 MPI_Recv(work_info.data(), 2, MPI_INT, 0, kTagExit, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
241 6 int escape_signal = work_info[0];
242 6 int row_to_process = work_info[1];
243
244
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (escape_signal == kEscapeSignal) {
245 break;
246 }
247
248
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 const int received_bytes = ReceiveImageData(local_input);
249 3 const int rows_received = received_bytes / (local_width * 3);
250
251
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (rows_received > 0) {
252
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 ProcessAndSendResult(local_width, local_input, local_result, rows_received, row_to_process);
253 }
254 }
255 3 }
256
257 3 int KapanovaSImageSmoothingMPI::ReceiveImageData(std::vector<uint8_t> &buffer) {
258 MPI_Status status;
259 3 MPI_Probe(0, kTagData, MPI_COMM_WORLD, &status);
260
261 3 int count = 0;
262 3 MPI_Get_count(&status, MPI_UNSIGNED_CHAR, &count);
263
264 3 buffer.resize(static_cast<size_t>(count));
265 3 MPI_Recv(buffer.data(), count, MPI_UNSIGNED_CHAR, 0, kTagData, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
266
267 3 return count;
268 }
269
270 3 void KapanovaSImageSmoothingMPI::ProcessAndSendResult(int local_width, const std::vector<uint8_t> &input,
271 std::vector<uint8_t> &result, int rows_received,
272 int row_to_process) {
273 const int target_row = row_to_process;
274
275
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 3 times.
14 for (int x_coord = 0; x_coord < local_width; ++x_coord) {
276 11 const auto pos = static_cast<size_t>(x_coord) * 3U;
277 11 SmoothPixel(&result[pos], x_coord, target_row, true, &input, local_width, rows_received);
278 }
279
280 3 MPI_Send(result.data(), local_width * 3, MPI_UNSIGNED_CHAR, 0, kTagResult, MPI_COMM_WORLD);
281 3 }
282
283 int KapanovaSImageSmoothingMPI::GetCommRank() {
284 6 int rank = 0;
285 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
286 6 return rank;
287 }
288
289 int KapanovaSImageSmoothingMPI::GetCommSize() {
290 9 int size = 0;
291 9 MPI_Comm_size(MPI_COMM_WORLD, &size);
292 3 return size;
293 }
294
295 6 bool KapanovaSImageSmoothingMPI::RunImpl() {
296 int rank = GetCommRank();
297 int size = GetCommSize();
298
299
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (size == 1) {
300 ProcessRowRange(1, height_ - 2);
301 ProcessBorderRows();
302 return true;
303 }
304
305
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (rank == 0) {
306 3 MasterProcess();
307 } else {
308 3 WorkerProcess();
309 }
310
311 6 MPI_Barrier(MPI_COMM_WORLD);
312
313 6 return true;
314 }
315
316 6 bool KapanovaSImageSmoothingMPI::PostProcessingImpl() {
317 6 GetOutput() = result_;
318 6 return true;
319 }
320
321 // Вспомогательные функции для уменьшения когнитивной сложности SmoothPixel
322 namespace {
323 99 void ProcessLocalKernelPixel(int ry, int rx, int radius, int k_size, int local_width, int local_height,
324 int local_y_center, int x_coord, const std::vector<uint8_t> &local_input,
325 const std::vector<float> &kernel, float &out_r, float &out_g, float &out_b) {
326 99 int local_y = local_y_center + ry;
327
328
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 if (local_y < 0 || local_y >= local_height) {
329 return;
330 }
331
332 auto clamp = [](int n, int lo, int hi) { return std::min(std::max(n, lo), hi); };
333 99 int local_x = clamp(x_coord + rx, 0, local_width - 1);
334
335 const int local_stride = local_width * 3;
336 99 const auto pixel_pos = static_cast<size_t>(local_y * local_stride) + (static_cast<size_t>(local_x) * 3U);
337 99 const auto kernel_pos = static_cast<size_t>((ry + radius) * k_size) + static_cast<size_t>(rx + radius);
338
339 99 out_r += static_cast<float>(local_input[pixel_pos]) * kernel[kernel_pos];
340 99 out_g += static_cast<float>(local_input[pixel_pos + 1U]) * kernel[kernel_pos];
341 99 out_b += static_cast<float>(local_input[pixel_pos + 2U]) * kernel[kernel_pos];
342 }
343
344 162 void ProcessGlobalKernelPixel(int ry, int rx, int radius, int k_size, int width, int height, int x_coord, int y_coord,
345 const std::vector<uint8_t> &input, const std::vector<float> &kernel, float &out_r,
346 float &out_g, float &out_b) {
347 auto clamp = [](int n, int lo, int hi) { return std::min(std::max(n, lo), hi); };
348 162 int global_y = clamp(y_coord + ry, 0, height - 1);
349 162 int global_x = clamp(x_coord + rx, 0, width - 1);
350
351 162 const auto pixel_pos = static_cast<size_t>(global_y * width * 3) + (static_cast<size_t>(global_x) * 3U);
352 162 const auto kernel_pos = static_cast<size_t>((ry + radius) * k_size) + static_cast<size_t>(rx + radius);
353
354 162 out_r += static_cast<float>(input[pixel_pos]) * kernel[kernel_pos];
355 162 out_g += static_cast<float>(input[pixel_pos + 1U]) * kernel[kernel_pos];
356 162 out_b += static_cast<float>(input[pixel_pos + 2U]) * kernel[kernel_pos];
357 162 }
358
359 29 void ApplyKernelToPixel(bool use_local, int radius, int width, int height, int x_coord, int y_coord,
360 const std::vector<uint8_t> &global_input, const std::vector<float> &kernel,
361 const std::vector<uint8_t> *local_input, int local_width, int local_height, float &out_r,
362 float &out_g, float &out_b) {
363 29 const int k_size = (2 * radius) + 1;
364
365
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 18 times.
29 if (use_local && local_input != nullptr) {
366 const int local_y_center = 1;
367
368
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 11 times.
44 for (int ry = -radius; ry <= radius; ++ry) {
369
2/2
✓ Branch 0 taken 99 times.
✓ Branch 1 taken 33 times.
132 for (int rx = -radius; rx <= radius; ++rx) {
370 99 ProcessLocalKernelPixel(ry, rx, radius, k_size, local_width, local_height, local_y_center, x_coord,
371 *local_input, kernel, out_r, out_g, out_b);
372 }
373 }
374 } else {
375
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 18 times.
72 for (int ry = -radius; ry <= radius; ++ry) {
376
2/2
✓ Branch 0 taken 162 times.
✓ Branch 1 taken 54 times.
216 for (int rx = -radius; rx <= radius; ++rx) {
377 162 ProcessGlobalKernelPixel(ry, rx, radius, k_size, width, height, x_coord, y_coord, global_input, kernel, out_r,
378 out_g, out_b);
379 }
380 }
381 }
382 29 }
383
384 29 void SetPixelValue(uint8_t *out, float out_r, float out_g, float out_b) {
385 29 out[0] = static_cast<uint8_t>(std::clamp(static_cast<int>(std::round(out_r)), 0, 255));
386 29 out[1] = static_cast<uint8_t>(std::clamp(static_cast<int>(std::round(out_g)), 0, 255));
387 29 out[2] = static_cast<uint8_t>(std::clamp(static_cast<int>(std::round(out_b)), 0, 255));
388 29 }
389 } // namespace
390
391 29 void KapanovaSImageSmoothingMPI::SmoothPixel(uint8_t *out, int x_coord, int y_coord, bool use_local,
392 const std::vector<uint8_t> *local_input, int local_width,
393 int local_height) {
394 29 float out_r = 0.0F;
395 29 float out_g = 0.0F;
396 29 float out_b = 0.0F;
397
398 29 ApplyKernelToPixel(use_local, radius_, width_, height_, x_coord, y_coord, input_, kernel_, local_input, local_width,
399 local_height, out_r, out_g, out_b);
400
401 29 SetPixelValue(out, out_r, out_g, out_b);
402 29 }
403
404 } // namespace kapanova_s_image_smoothing
405