diff --git a/tasks/kondrashova_v_marking_components/all/include/ops_all.hpp b/tasks/kondrashova_v_marking_components/all/include/ops_all.hpp new file mode 100644 index 000000000..801d92d1c --- /dev/null +++ b/tasks/kondrashova_v_marking_components/all/include/ops_all.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include "kondrashova_v_marking_components/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kondrashova_v_marking_components { + +class KondrashovaVTaskALL : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kALL; + } + explicit KondrashovaVTaskALL(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + int width_{}; + int height_{}; + int rank_{}; + int world_size_{}; + std::vector image_; + std::vector labels_1d_; +}; + +} // namespace kondrashova_v_marking_components diff --git a/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp new file mode 100644 index 000000000..7b451be78 --- /dev/null +++ b/tasks/kondrashova_v_marking_components/all/src/ops_all.cpp @@ -0,0 +1,334 @@ +#include "kondrashova_v_marking_components/all/include/ops_all.hpp" + +#include + +#include +#include +#include +#include + +#include "kondrashova_v_marking_components/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace kondrashova_v_marking_components { + +KondrashovaVTaskALL::KondrashovaVTaskALL(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = {}; +} + +bool KondrashovaVTaskALL::ValidationImpl() { + return true; +} + +bool KondrashovaVTaskALL::PreProcessingImpl() { + MPI_Comm_rank(MPI_COMM_WORLD, &rank_); + MPI_Comm_size(MPI_COMM_WORLD, &world_size_); + + if (rank_ == 0) { + const auto &in = GetInput(); + width_ = in.width; + height_ = in.height; + image_ = in.data; + } + + MPI_Bcast(&width_, 1, MPI_INT, 0, MPI_COMM_WORLD); + MPI_Bcast(&height_, 1, MPI_INT, 0, MPI_COMM_WORLD); + + int has_valid_input = 0; + if (rank_ == 0) { + has_valid_input = (width_ > 0 && height_ > 0 && static_cast(image_.size()) == (width_ * height_)) ? 1 : 0; + } + MPI_Bcast(&has_valid_input, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (has_valid_input == 0) { + image_.clear(); + labels_1d_.clear(); + GetOutput().count = 0; + GetOutput().labels.clear(); + return true; + } + + if (rank_ != 0) { + image_.resize(static_cast(width_) * static_cast(height_)); + } + + MPI_Bcast(image_.data(), width_ * height_, MPI_UNSIGNED_CHAR, 0, MPI_COMM_WORLD); + + labels_1d_.assign(static_cast(width_) * static_cast(height_), 0); + + GetOutput().count = 0; + GetOutput().labels.clear(); + return true; +} + +namespace { + +int Find(std::vector &parent, int xx) { + while (parent[static_cast(xx)] != xx) { + parent[static_cast(xx)] = parent[static_cast(parent[static_cast(xx)])]; + xx = parent[static_cast(xx)]; + } + return xx; +} + +void Unite(std::vector &parent, std::vector &rnk, int aa, int bb) { + aa = Find(parent, aa); + bb = Find(parent, bb); + if (aa == bb) { + return; + } + if (rnk[static_cast(aa)] < rnk[static_cast(bb)]) { + std::swap(aa, bb); + } + parent[static_cast(bb)] = aa; + if (rnk[static_cast(aa)] == rnk[static_cast(bb)]) { + rnk[static_cast(aa)]++; + } +} + +int GetNeighborLabel(int ii, int jj, int di, int dj, int row_start, int row_end, int width, + const std::vector &image, const std::vector &local_labels) { + int ni = ii + di; + int nj = jj + dj; + if (ni < row_start || ni >= row_end || nj < 0 || nj >= width) { + return 0; + } + auto nidx = (static_cast(ni) * static_cast(width)) + static_cast(nj); + if (image[nidx] == 0) { + return local_labels[nidx]; + } + return 0; +} + +void ScanStripe(int row_start, int row_end, int width, int label_offset, const std::vector &image, + std::vector &local_labels) { + int current_label = label_offset; + for (int ii = row_start; ii < row_end; ++ii) { + for (int jj = 0; jj < width; ++jj) { + auto idx = (static_cast(ii) * static_cast(width)) + static_cast(jj); + if (image[idx] != 0) { + continue; + } + + int left_label = GetNeighborLabel(ii, jj, 0, -1, row_start, row_end, width, image, local_labels); + int top_label = GetNeighborLabel(ii, jj, -1, 0, row_start, row_end, width, image, local_labels); + + if (left_label == 0 && top_label == 0) { + local_labels[idx] = ++current_label; + } else if (left_label != 0 && top_label == 0) { + local_labels[idx] = left_label; + } else if (left_label == 0) { + local_labels[idx] = top_label; + } else { + local_labels[idx] = std::min(left_label, top_label); + } + } + } +} + +void MergeHorizontal(int height, int width, const std::vector &local_labels, std::vector &parent, + std::vector &rnk) { + for (int ii = 0; ii < height; ++ii) { + for (int jj = 1; jj < width; ++jj) { + auto idx = (static_cast(ii) * static_cast(width)) + static_cast(jj); + auto lidx = (static_cast(ii) * static_cast(width)) + static_cast(jj - 1); + if (local_labels[idx] != 0 && local_labels[lidx] != 0 && local_labels[idx] != local_labels[lidx]) { + Unite(parent, rnk, local_labels[idx], local_labels[lidx]); + } + } + } +} + +void MergeVertical(int height, int width, const std::vector &local_labels, std::vector &parent, + std::vector &rnk) { + for (int ii = 1; ii < height; ++ii) { + for (int jj = 0; jj < width; ++jj) { + auto idx = (static_cast(ii) * static_cast(width)) + static_cast(jj); + auto tidx = (static_cast(ii - 1) * static_cast(width)) + static_cast(jj); + if (local_labels[idx] != 0 && local_labels[tidx] != 0 && local_labels[idx] != local_labels[tidx]) { + Unite(parent, rnk, local_labels[idx], local_labels[tidx]); + } + } + } +} + +void MergeBoundaries(int row_start, int row_end, int width, int num_threads, const std::vector &local_labels, + std::vector &parent, std::vector &rnk) { + const int rows = row_end - row_start; + for (int tid = 1; tid < num_threads; ++tid) { + const int boundary_row = row_start + ((tid * rows) / num_threads); + if (boundary_row <= row_start || boundary_row >= row_end) { + continue; + } + for (int jj = 0; jj < width; ++jj) { + auto idx = (static_cast(boundary_row) * static_cast(width)) + static_cast(jj); + auto tidx = (static_cast(boundary_row - 1) * static_cast(width)) + static_cast(jj); + if (local_labels[idx] != 0 && local_labels[tidx] != 0 && local_labels[idx] != local_labels[tidx]) { + Unite(parent, rnk, local_labels[idx], local_labels[tidx]); + } + } + } +} + +int Relabel(int total, const std::vector &local_labels, std::vector &parent, std::vector &relabel_map, + std::vector &labels_1d) { + int count = 0; + for (int ii = 0; ii < total; ++ii) { + auto idx = static_cast(ii); + if (local_labels[idx] == 0) { + continue; + } + int root = Find(parent, local_labels[idx]); + if (relabel_map[static_cast(root)] == 0) { + relabel_map[static_cast(root)] = ++count; + } + labels_1d[idx] = relabel_map[static_cast(root)]; + } + return count; +} + +void FillRowLabels(int width, int row, const std::vector &local_labels, std::vector &row_labels) { + for (int jj = 0; jj < width; ++jj) { + row_labels[static_cast(jj)] = + local_labels[(static_cast(row) * static_cast(width)) + static_cast(jj)]; + } +} + +void MergeBoundaryLabels(int width, const std::vector &send_row, const std::vector &recv_row, + std::vector &parent, std::vector &rnk) { + for (int jj = 0; jj < width; ++jj) { + const int label_cur = send_row[static_cast(jj)]; + const int label_neighbor = recv_row[static_cast(jj)]; + if (label_cur != 0 && label_neighbor != 0 && label_cur != label_neighbor) { + Unite(parent, rnk, label_cur, label_neighbor); + } + } +} + +void ExchangeAndMergeRow(int width, int neighbor_rank, int send_tag, int recv_tag, bool has_rows, int row_index, + const std::vector &local_labels, std::vector &parent, std::vector &rnk) { + std::vector send_row(static_cast(width), 0); + std::vector recv_row(static_cast(width)); + + if (has_rows) { + FillRowLabels(width, row_index, local_labels, send_row); + } + + MPI_Sendrecv(send_row.data(), width, MPI_INT, neighbor_rank, send_tag, recv_row.data(), width, MPI_INT, neighbor_rank, + recv_tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + MergeBoundaryLabels(width, send_row, recv_row, parent, rnk); +} + +void MergeMPIBoundaries(int width, int rank, int world_size, int local_row_start, int local_row_end, + const std::vector &local_labels, std::vector &parent, std::vector &rnk) { + const bool has_rows = local_row_start < local_row_end; + const int first_row = local_row_start; + const int last_row = has_rows ? (local_row_end - 1) : local_row_start; + + if (rank > 0) { + ExchangeAndMergeRow(width, rank - 1, 0, 1, has_rows, first_row, local_labels, parent, rnk); + } + + if (rank < world_size - 1) { + ExchangeAndMergeRow(width, rank + 1, 1, 0, has_rows, last_row, local_labels, parent, rnk); + } +} + +} // namespace + +bool KondrashovaVTaskALL::RunImpl() { + if (width_ <= 0 || height_ <= 0 || image_.empty()) { + GetOutput().count = 0; + return true; + } + + const int total = width_ * height_; + const int num_threads = ppc::util::GetNumThreads(); + + const int mpi_row_start = (rank_ * height_) / world_size_; + const int mpi_row_end = ((rank_ + 1) * height_) / world_size_; + const int mpi_rows = mpi_row_end - mpi_row_start; + + const int max_rows_per_rank = (height_ + world_size_ - 1) / world_size_; + const int max_labels_per_thread = (max_rows_per_rank * width_) + 1; + + std::vector local_labels(static_cast(total), 0); + + const int width = width_; + const std::vector image = image_; + +#pragma omp parallel num_threads(num_threads) default(none) shared(local_labels, width, image, num_threads) \ + firstprivate(mpi_row_start, mpi_rows, max_labels_per_thread) + { + const int tid = omp_get_thread_num(); + const int omp_row_start = mpi_row_start + ((tid * mpi_rows) / num_threads); + const int omp_row_end = mpi_row_start + (((tid + 1) * mpi_rows) / num_threads); + const int label_offset = (rank_ * num_threads * max_labels_per_thread) + (tid * max_labels_per_thread); + ScanStripe(omp_row_start, omp_row_end, width, label_offset, image, local_labels); + } + + const int global_max_labels = (world_size_ * num_threads * max_labels_per_thread) + 1; + std::vector parent(static_cast(global_max_labels)); + std::vector rnk(static_cast(global_max_labels), 0); + for (int ii = 0; ii < global_max_labels; ++ii) { + parent[static_cast(ii)] = ii; + } + + MergeHorizontal(height_, width_, local_labels, parent, rnk); + MergeVertical(height_, width_, local_labels, parent, rnk); + MergeBoundaries(mpi_row_start, mpi_row_end, width_, num_threads, local_labels, parent, rnk); + + MergeMPIBoundaries(width_, rank_, world_size_, mpi_row_start, mpi_row_end, local_labels, parent, rnk); + + MPI_Barrier(MPI_COMM_WORLD); + + std::vector all_local_labels(static_cast(total), 0); + MPI_Allreduce(local_labels.data(), all_local_labels.data(), total, MPI_INT, MPI_MAX, MPI_COMM_WORLD); + + if (rank_ == 0) { + std::vector global_parent(static_cast(global_max_labels)); + std::vector global_rnk(static_cast(global_max_labels), 0); + for (int ii = 0; ii < global_max_labels; ++ii) { + global_parent[static_cast(ii)] = ii; + } + + MergeHorizontal(height_, width_, all_local_labels, global_parent, global_rnk); + MergeVertical(height_, width_, all_local_labels, global_parent, global_rnk); + + std::vector relabel_map(static_cast(global_max_labels), 0); + GetOutput().count = Relabel(total, all_local_labels, global_parent, relabel_map, labels_1d_); + } + + int global_count = GetOutput().count; + MPI_Bcast(&global_count, 1, MPI_INT, 0, MPI_COMM_WORLD); + GetOutput().count = global_count; + if (total > 0) { + if (labels_1d_.size() != static_cast(total)) { + labels_1d_.assign(static_cast(total), 0); + } + MPI_Bcast(labels_1d_.data(), total, MPI_INT, 0, MPI_COMM_WORLD); + } + + return true; +} + +bool KondrashovaVTaskALL::PostProcessingImpl() { + if (width_ <= 0 || height_ <= 0) { + GetOutput().labels.clear(); + return true; + } + + GetOutput().labels.assign(height_, std::vector(width_, 0)); + for (int ii = 0; ii < height_; ++ii) { + for (int jj = 0; jj < width_; ++jj) { + auto idx = (static_cast(ii) * static_cast(width_)) + static_cast(jj); + GetOutput().labels[ii][jj] = labels_1d_[idx]; + } + } + return true; +} + +} // namespace kondrashova_v_marking_components diff --git a/tasks/kondrashova_v_marking_components/report.md b/tasks/kondrashova_v_marking_components/report.md new file mode 100644 index 000000000..bde17adb3 --- /dev/null +++ b/tasks/kondrashova_v_marking_components/report.md @@ -0,0 +1,248 @@ +# Маркировка компонент связности на бинарном изображении + +- Студент: Кондрашова Виктория Андреевна +- Группа: 3823Б1ПР1 +- Технологии: SEQ, OMP, TBB, STL, ALL + +## 1. Введение + +Задача маркировки компонент связности возникает при обработке изображений, +компьютерном зрении и анализе бинарных масок. Требуется выделить на бинарном +изображении все области связных пикселей и присвоить каждой области свой номер. + +Цель работы: реализовать последовательную и параллельные версии алгоритма +маркировки компонент связности для бинарного изображения с использованием +технологий OpenMP, TBB, STL и комбинированной схемы MPI + OpenMP. + +## 2. Постановка задачи + +**Вход:** + +- Бинарное изображение, заданное структурой `ImageData` +- Одномерный массив пикселей `data` +- Размеры изображения `width` и `height` + +В текущей реализации пиксель со значением `0` считается частью объекта, +а пиксель `1` считается фоном. + +**Выход:** + +- Количество компонент связности `count` +- Матрица меток `labels`, где каждая компонента имеет свой положительный номер, + а фон имеет метку `0` + +**Ограничения:** + +- Размеры изображения должны быть неотрицательными +- Размер массива `data` должен соответствовать `width * height` +- Для пустого изображения результатом является нулевое число компонент + +## 3. Базовый алгоритм + +В последовательной версии используется обход в ширину по 4-связности: + +1. Выполняется проход по всем пикселям изображения. +2. Если найден пиксель объекта (`0`), который еще не был размечен, создается новая метка. +3. Из этого пикселя запускается BFS, который посещает всех 4-соседей + (вверх, вниз, влево, вправо), принадлежащих той же компоненте. +4. Все достигнутые пиксели получают одну и ту же метку. +5. После завершения обхода формируется двумерная матрица меток. + +Сложность последовательного алгоритма составляет `O(width * height)`, +так как каждый пиксель посещается ограниченное число раз. + +## 4. Схема распараллеливания + +Во всех параллельных реализациях используется общая идея: + +1. Изображение делится на горизонтальные полосы. +2. Каждая полоса независимо сканируется и получает временные метки. +3. Для объединения эквивалентных меток применяется структура DSU + (disjoint set union, union-find). +4. После локальной разметки объединяются эквивалентные метки: + внутри строки, на границах полос, а в версии ALL также между MPI-процессами. +5. Затем выполняется перенумерация компонент в компактный диапазон `1..count`. + +### 4.1 OMP + +OpenMP-версия создает параллельную область, в которой потоки обрабатывают свои +диапазоны строк. После локального сканирования в одном потоке выполняется +слияние меток на границах полос и итоговая перенумерация. + +### 4.2 TBB + +В версии TBB используется `tbb::task_arena` для задания числа потоков и +`tbb::parallel_for` по диапазону идентификаторов полос. Каждый поток выполняет +ту же локальную разметку, что и в OMP-реализации, после чего выполняется +последовательное объединение эквивалентных меток. + +### 4.3 STL + +В STL-реализации создается набор потоков `std::thread`, каждый из которых +размечает свою горизонтальную полосу. После `join()` выполняются объединение +меток через DSU и окончательная перенумерация. + +### 4.4 ALL + +Комбинированная версия использует два уровня параллелизма: + +- **MPI:** процесс `0` считывает входные данные, затем размеры и все изображение + рассылаются всем процессам через `MPI_Bcast`. Каждый процесс обрабатывает + только свой диапазон строк. Для объединения компонент на границе процессов + используется обмен граничными строками через `MPI_Sendrecv`. Далее локальные + метки агрегируются с помощью `MPI_Allreduce`. +- **OpenMP:** внутри каждого MPI-процесса его диапазон строк дополнительно + делится между потоками. + +## 5. Детали реализации + +### Структура файлов + +- `common/include/common.hpp` — типы входных и выходных данных +- `seq/` — последовательная реализация на BFS +- `omp/` — реализация с OpenMP +- `tbb/` — реализация с TBB +- `stl/` — реализация на `std::thread` +- `all/` — гибридная реализация MPI + OpenMP +- `tests/functional/main.cpp` — функциональные тесты +- `tests/performance/main.cpp` — тесты производительности + +### Ключевые структуры и классы + +- `ImageData` — входное изображение: `data`, `width`, `height` +- `Result` — результат: число компонент `count` и матрица меток `labels` +- `KondrashovaVTaskSEQ` +- `KondrashovaVTaskOMP` +- `KondrashovaVTaskTBB` +- `KondrashovaVTaskSTL` +- `KondrashovaVTaskALL` + +Каждый класс наследуется от `BaseTask` и реализует стандартные этапы: + +- `ValidationImpl()` +- `PreProcessingImpl()` +- `RunImpl()` +- `PostProcessingImpl()` + +В параллельных версиях ключевые вспомогательные функции вынесены отдельно: + +- `Find()` и `Unite()` — операции DSU +- `ScanStripe()` — локальная разметка полосы +- `MergeHorizontal()` — объединение меток внутри строк +- `MergeBoundaries()` — объединение меток на границах полос +- `Relabel()` — финальная перенумерация меток + +В версии ALL дополнительно реализовано: + +- `MergeMPIBoundaries()` — объединение компонент на границах между MPI-процессами + +## 6. Экспериментальная установка + +### Аппаратное и программное окружение + +| Параметр | Значение | +|--------------------|-------------------------------| +| Процессор | AMD Ryzen 5 5600H | +|Операционная система| Windows 10 Pro 22H2 | +| Компилятор |MSVC 19.37 (Visual Studio 2022)| +| Тип сборки | Release | + +Для проверки использовались команды: + +- `ppc_func_tests.exe --gtest_filter="*Kondrashova*"` +- `ppc_perf_tests.exe --gtest_filter="*Kondrashova*"` + +## 7. Результаты + +### 7.1 Проверка корректности + +Функциональные тесты покрывают 13 сценариев: + +- нулевая ширина +- нулевая высота +- пустое изображение +- одна компонента +- изолированные пиксели +- две области +- U-образная область +- сложная конфигурация +- одна строка с разрывами +- один столбец с разрывами +- случай с объединением временных меток +- компоненту, пересекающую границы полос +- крупный сложный пример `30 x 30` + +Для всех технологий проверяются: + +- ожидаемое количество компонент +- корректный размер выходной матрицы меток +- согласованность меток с фоном и объектом + +Фактический результат запуска: + +- выполнено **65** функциональных теста +- успешно пройдено **65 / 65** + +Присутствовали тесты для `SEQ`, `OMP`, `STL`, `TBB` и `ALL`. + +### 7.2 Производительность + +Производительность измерялась на изображениях размером 512×512 для шести типов +данных (all ones, all zeros, chessboard, sparse dots, stripes, blocks). +Замерялось время выполнения в режиме `task_run`, ускорение рассчитано +относительно SEQ для каждого изображения. + +|Изображение| Версия |Время, с|Ускорение| +|-----------|-----------|--------|---------| +| all ones | SEQ |0.000319| 1.000 | +| all ones | OMP |0.003854| 0.083 | +| all ones | TBB |0.003229| 0.099 | +| all ones | STL |0.004231| 0.075 | +| all ones |ALL (2 MPI)|0.004511| 0.071 | +| all zeros | SEQ |0.001509| 1.000 | +| all zeros | OMP |0.004510| 0.335 | +| all zeros | TBB |0.004198| 0.359 | +| all zeros | STL |0.005421| 0.278 | +| all zeros |ALL (2 MPI)|0.004339| 0.348 | +| chessboard| SEQ |0.009387| 1.000 | +| chessboard| OMP |0.003941| 2.382 | +| chessboard| TBB |0.003911| 2.400 | +| chessboard| STL |0.004667| 2.011 | +| chessboard|ALL (2 MPI)|0.003977| 2.361 | +|sparse dots| SEQ |0.000575| 1.000 | +|sparse dots| OMP |0.003556| 0.162 | +|sparse dots| TBB |0.003075| 0.187 | +|sparse dots| STL |0.003491| 0.165 | +|sparse dots|ALL (2 MPI)|0.003415| 0.168 | +| stripes | SEQ |0.000717| 1.000 | +| stripes | OMP |0.003375| 0.212 | +| stripes | TBB |0.003381| 0.212 | +| stripes | STL |0.003763| 0.191 | +| stripes |ALL (2 MPI)|0.003683| 0.195 | +| blocks | SEQ |0.000770| 1.000 | +| blocks | OMP |0.003507| 0.220 | +| blocks | TBB |0.003877| 0.199 | +| blocks | STL |0.004431| 0.174 | +| blocks |ALL (2 MPI)|0.003790| 0.203 | + +## 8. Выводы + +Реализованы последовательная и четыре параллельные версии алгоритма +маркировки компонент связности. Все реализации прошли функциональное +тестирование и дали корректные результаты. + +На изображении типа chessboard параллельные версии показывают ускорение +порядка 2–2.4 раза, что объясняется равномерной нагрузкой и большим числом +компонент. На более простых изображениях накладные расходы на синхронизацию +превышают выигрыш от распараллеливания, что приводит к замедлению. + +Гибридная ALL-версия корректно работает при запуске через MPI и показывает +сопоставимые результаты с потоковыми версиями на указанной конфигурации. + +## 9. Источники + +1. OpenMP API Specification — +2. oneTBB Documentation — +3. MPI Forum. MPI Standard — +4. Документация по `std::thread` — +5. Материалы курса по параллельному программированию — diff --git a/tasks/kondrashova_v_marking_components/stl/include/ops_stl.hpp b/tasks/kondrashova_v_marking_components/stl/include/ops_stl.hpp new file mode 100644 index 000000000..6979e1d5b --- /dev/null +++ b/tasks/kondrashova_v_marking_components/stl/include/ops_stl.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "kondrashova_v_marking_components/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace kondrashova_v_marking_components { + +class KondrashovaVTaskSTL : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSTL; + } + explicit KondrashovaVTaskSTL(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + int width_{}; + int height_{}; + std::vector image_; + std::vector labels_1d_; +}; + +} // namespace kondrashova_v_marking_components diff --git a/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp b/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp new file mode 100644 index 000000000..4202ad274 --- /dev/null +++ b/tasks/kondrashova_v_marking_components/stl/src/ops_stl.cpp @@ -0,0 +1,209 @@ +#include "kondrashova_v_marking_components/stl/include/ops_stl.hpp" + +#include +#include +#include +#include +#include +#include + +#include "kondrashova_v_marking_components/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace kondrashova_v_marking_components { + +KondrashovaVTaskSTL::KondrashovaVTaskSTL(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = {}; +} + +bool KondrashovaVTaskSTL::ValidationImpl() { + return true; +} + +bool KondrashovaVTaskSTL::PreProcessingImpl() { + const auto &in = GetInput(); + + width_ = in.width; + height_ = in.height; + image_ = in.data; + + labels_1d_.assign(static_cast(width_) * static_cast(height_), 0); + + GetOutput().count = 0; + GetOutput().labels.clear(); + return true; +} + +namespace { + +int Find(std::vector &parent, int xx) { + while (parent[static_cast(xx)] != xx) { + parent[static_cast(xx)] = parent[static_cast(parent[static_cast(xx)])]; + xx = parent[static_cast(xx)]; + } + return xx; +} + +void Unite(std::vector &parent, std::vector &rnk, int aa, int bb) { + aa = Find(parent, aa); + bb = Find(parent, bb); + if (aa == bb) { + return; + } + if (rnk[static_cast(aa)] < rnk[static_cast(bb)]) { + std::swap(aa, bb); + } + parent[static_cast(bb)] = aa; + if (rnk[static_cast(aa)] == rnk[static_cast(bb)]) { + rnk[static_cast(aa)]++; + } +} + +int GetNeighborLabel(int ii, int jj, int di, int dj, int row_start, int row_end, int width, + const std::vector &image, const std::vector &local_labels) { + int ni = ii + di; + int nj = jj + dj; + if (ni < row_start || ni >= row_end || nj < 0 || nj >= width) { + return 0; + } + auto nidx = (static_cast(ni) * static_cast(width)) + static_cast(nj); + if (image[nidx] == 0) { + return local_labels[nidx]; + } + return 0; +} + +void ScanStripe(int row_start, int row_end, int width, int label_offset, const std::vector &image, + std::vector &local_labels) { + int current_label = label_offset; + for (int ii = row_start; ii < row_end; ++ii) { + for (int jj = 0; jj < width; ++jj) { + auto idx = (static_cast(ii) * static_cast(width)) + static_cast(jj); + if (image[idx] != 0) { + continue; + } + + int left_label = GetNeighborLabel(ii, jj, 0, -1, row_start, row_end, width, image, local_labels); + int top_label = GetNeighborLabel(ii, jj, -1, 0, row_start, row_end, width, image, local_labels); + + if (left_label == 0 && top_label == 0) { + local_labels[idx] = ++current_label; + } else if (left_label != 0 && top_label == 0) { + local_labels[idx] = left_label; + } else if (left_label == 0) { + local_labels[idx] = top_label; + } else { + local_labels[idx] = std::min(left_label, top_label); + } + } + } +} + +void MergeHorizontal(int height, int width, const std::vector &local_labels, std::vector &parent, + std::vector &rnk) { + for (int ii = 0; ii < height; ++ii) { + for (int jj = 1; jj < width; ++jj) { + auto idx = (static_cast(ii) * static_cast(width)) + static_cast(jj); + auto lidx = (static_cast(ii) * static_cast(width)) + static_cast(jj - 1); + if (local_labels[idx] != 0 && local_labels[lidx] != 0 && local_labels[idx] != local_labels[lidx]) { + Unite(parent, rnk, local_labels[idx], local_labels[lidx]); + } + } + } +} + +void MergeBoundaries(int height, int width, int num_threads, const std::vector &local_labels, + std::vector &parent, std::vector &rnk) { + for (int tid = 1; tid < num_threads; ++tid) { + const int boundary_row = (tid * height) / num_threads; + if (boundary_row >= height) { + continue; + } + for (int jj = 0; jj < width; ++jj) { + auto idx = (static_cast(boundary_row) * static_cast(width)) + static_cast(jj); + auto tidx = (static_cast(boundary_row - 1) * static_cast(width)) + static_cast(jj); + if (local_labels[idx] != 0 && local_labels[tidx] != 0 && local_labels[idx] != local_labels[tidx]) { + Unite(parent, rnk, local_labels[idx], local_labels[tidx]); + } + } + } +} + +int Relabel(int total, const std::vector &local_labels, std::vector &parent, std::vector &relabel_map, + std::vector &labels_1d) { + int count = 0; + for (int ii = 0; ii < total; ++ii) { + auto idx = static_cast(ii); + if (local_labels[idx] == 0) { + continue; + } + int root = Find(parent, local_labels[idx]); + if (relabel_map[static_cast(root)] == 0) { + relabel_map[static_cast(root)] = ++count; + } + labels_1d[idx] = relabel_map[static_cast(root)]; + } + return count; +} + +} // namespace + +bool KondrashovaVTaskSTL::RunImpl() { + if (width_ <= 0 || height_ <= 0 || image_.empty()) { + GetOutput().count = 0; + return true; + } + + const int total = width_ * height_; + const int num_threads = ppc::util::GetNumThreads(); + const int max_labels_per_thread = total + 1; + const int max_total_labels = (num_threads * max_labels_per_thread) + 1; + + std::vector local_labels(static_cast(total), 0); + + std::vector threads(static_cast(num_threads)); + for (int tid = 0; tid < num_threads; ++tid) { + const int row_start = (tid * height_) / num_threads; + const int row_end = ((tid + 1) * height_) / num_threads; + const int label_offset = tid * max_labels_per_thread; + threads[static_cast(tid)] = + std::thread(ScanStripe, row_start, row_end, width_, label_offset, std::cref(image_), std::ref(local_labels)); + } + for (auto &thr : threads) { + thr.join(); + } + + std::vector parent(static_cast(max_total_labels)); + std::vector rnk(static_cast(max_total_labels), 0); + for (int ii = 0; ii < max_total_labels; ++ii) { + parent[static_cast(ii)] = ii; + } + + MergeHorizontal(height_, width_, local_labels, parent, rnk); + MergeBoundaries(height_, width_, num_threads, local_labels, parent, rnk); + + std::vector relabel_map(static_cast(max_total_labels), 0); + GetOutput().count = Relabel(total, local_labels, parent, relabel_map, labels_1d_); + + return true; +} + +bool KondrashovaVTaskSTL::PostProcessingImpl() { + if (width_ <= 0 || height_ <= 0) { + GetOutput().labels.clear(); + return true; + } + + GetOutput().labels.assign(height_, std::vector(width_, 0)); + for (int ii = 0; ii < height_; ++ii) { + for (int jj = 0; jj < width_; ++jj) { + auto idx = (static_cast(ii) * static_cast(width_)) + static_cast(jj); + GetOutput().labels[ii][jj] = labels_1d_[idx]; + } + } + return true; +} + +} // namespace kondrashova_v_marking_components diff --git a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp index ea3b704c3..299cd6a24 100644 --- a/tasks/kondrashova_v_marking_components/tests/functional/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/functional/main.cpp @@ -6,9 +6,11 @@ #include #include +#include "kondrashova_v_marking_components/all/include/ops_all.hpp" #include "kondrashova_v_marking_components/common/include/common.hpp" #include "kondrashova_v_marking_components/omp/include/ops_omp.hpp" #include "kondrashova_v_marking_components/seq/include/ops_seq.hpp" +#include "kondrashova_v_marking_components/stl/include/ops_stl.hpp" #include "kondrashova_v_marking_components/tbb/include/ops_tbb.hpp" #include "util/include/func_test_util.hpp" @@ -17,6 +19,9 @@ namespace kondrashova_v_marking_components { namespace { int GetExpectedCount(const std::string &type) { + if (type == "zero_width" || type == "zero_height") { + return 0; + } if (type == "one_component") { return 1; } @@ -26,10 +31,28 @@ int GetExpectedCount(const std::string &type) { if (type == "two_regions") { return 2; } + if (type == "u_shape") { + return 1; + } + if (type == "complex") { + return 2; + } + if (type == "single_row_gaps" || type == "single_column_gaps") { + return 2; + } + if (type == "merge_labels" || type == "boundary_bridge") { + return 1; + } + if (type == "large_complex") { + return 4; + } return 0; } bool CheckLabelsSize(const OutType &output_data, const InType &image) { + if (image.width <= 0 || image.height <= 0) { + return output_data.labels.empty(); + } if (output_data.labels.size() != static_cast(image.height)) { return false; } @@ -40,23 +63,56 @@ bool CheckLabelsSize(const OutType &output_data, const InType &image) { } bool CheckLabelsValues(const OutType &output_data, const InType &image) { + if (image.width <= 0 || image.height <= 0) { + return true; + } for (int ii = 0; ii < image.height; ++ii) { for (int jj = 0; jj < image.width; ++jj) { auto idx = (static_cast(ii) * static_cast(image.width)) + static_cast(jj); - if (image.data[idx] == 1) { - if (output_data.labels[ii][jj] != 0) { - return false; - } - } else { - if (output_data.labels[ii][jj] <= 0) { - return false; - } + const bool is_background = image.data[idx] == 1; + const bool label_is_valid = is_background ? (output_data.labels[ii][jj] == 0) : (output_data.labels[ii][jj] > 0); + if (!label_is_valid) { + return false; } } } return true; } +InType MakeLargeComplexTestImage() { + constexpr int kSize = 30; + InType image{}; + image.width = kSize; + image.height = kSize; + image.data.assign(static_cast(kSize) * static_cast(kSize), 1); + + for (int i = 0; i < kSize; ++i) { + image.data[static_cast(i)] = 0; + } + + for (int i = 5; i < 25; ++i) { + image.data[(i * kSize) + 15] = 0; + } + + for (int i = 25; i <= 27; ++i) { + for (int j = 25; j <= 27; ++j) { + image.data[(i * kSize) + j] = 0; + } + } + + for (int i = 10; i < 20; ++i) { + image.data[(i * kSize) + 5] = 0; + } + for (int i = 10; i < 20; ++i) { + image.data[(i * kSize) + 10] = 0; + } + for (int j = 5; j <= 10; ++j) { + image.data[(19 * kSize) + j] = 0; + } + + return image; +} + } // namespace class MarkingComponentsFuncTest : public ppc::util::BaseRunFuncTests { @@ -86,7 +142,13 @@ class MarkingComponentsFuncTest : public ppc::util::BaseRunFuncTests(GetParam()); InType image{}; - if (type == "empty") { + if (type == "zero_width") { + image.width = 0; + image.height = 3; + } else if (type == "zero_height") { + image.width = 3; + image.height = 0; + } else if (type == "empty") { image.data = {1, 1, 1, 1, 1, 1, 1, 1, 1}; image.width = 3; image.height = 3; @@ -102,6 +164,32 @@ class MarkingComponentsFuncTest : public ppc::util::BaseRunFuncTests kTestParam = {std::make_tuple(0, "empty"), std::make_tuple(1, "one_component"), - std::make_tuple(2, "isolated_pixels"), std::make_tuple(3, "two_regions")}; +const std::array kTestParam = { + std::make_tuple(0, "zero_width"), std::make_tuple(1, "zero_height"), + std::make_tuple(2, "empty"), std::make_tuple(3, "one_component"), + std::make_tuple(4, "isolated_pixels"), std::make_tuple(5, "two_regions"), + std::make_tuple(6, "u_shape"), std::make_tuple(7, "complex"), + std::make_tuple(8, "single_row_gaps"), std::make_tuple(9, "single_column_gaps"), + std::make_tuple(10, "merge_labels"), std::make_tuple(11, "boundary_bridge"), + std::make_tuple(12, "large_complex")}; const auto kTestTasksList = std::tuple_cat( ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), - ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components)); + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_kondrashova_v_marking_components)); INSTANTIATE_TEST_SUITE_P(KondrashovaVMarkingComponentsFunctionalTests, MarkingComponentsFuncTest, ppc::util::ExpandToValues(kTestTasksList), diff --git a/tasks/kondrashova_v_marking_components/tests/performance/main.cpp b/tasks/kondrashova_v_marking_components/tests/performance/main.cpp index 76ebe7335..687f648a2 100644 --- a/tasks/kondrashova_v_marking_components/tests/performance/main.cpp +++ b/tasks/kondrashova_v_marking_components/tests/performance/main.cpp @@ -5,9 +5,11 @@ #include #include +#include "kondrashova_v_marking_components/all/include/ops_all.hpp" #include "kondrashova_v_marking_components/common/include/common.hpp" #include "kondrashova_v_marking_components/omp/include/ops_omp.hpp" #include "kondrashova_v_marking_components/seq/include/ops_seq.hpp" +#include "kondrashova_v_marking_components/stl/include/ops_stl.hpp" #include "kondrashova_v_marking_components/tbb/include/ops_tbb.hpp" #include "performance/include/performance.hpp" #include "util/include/perf_test_util.hpp" @@ -213,9 +215,9 @@ TEST_P(BlocksPerfTest, RunPerfModes) { namespace { -const auto kAllPerfTasks = - ppc::util::MakeAllPerfTasks( - PPC_SETTINGS_kondrashova_v_marking_components); +const auto kAllPerfTasks = ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_kondrashova_v_marking_components); const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks);