diff --git a/tasks/vinyaikina_e_vert_ribbon_scheme/common/include/common.hpp b/tasks/vinyaikina_e_vert_ribbon_scheme/common/include/common.hpp new file mode 100644 index 00000000..fc9ebe7a --- /dev/null +++ b/tasks/vinyaikina_e_vert_ribbon_scheme/common/include/common.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace vinyaikina_e_vert_ribbon_scheme { + +using InType = int; +using OutType = int; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace vinyaikina_e_vert_ribbon_scheme diff --git a/tasks/vinyaikina_e_vert_ribbon_scheme/info.json b/tasks/vinyaikina_e_vert_ribbon_scheme/info.json new file mode 100644 index 00000000..1181b586 --- /dev/null +++ b/tasks/vinyaikina_e_vert_ribbon_scheme/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Екатерина", + "group_number": "3823Б1ПР3", + "last_name": "Виняйкина", + "middle_name": "Александровна", + "task_number": "2" + } +} diff --git a/tasks/vinyaikina_e_vert_ribbon_scheme/mpi/include/ops_mpi.hpp b/tasks/vinyaikina_e_vert_ribbon_scheme/mpi/include/ops_mpi.hpp new file mode 100644 index 00000000..a239bb6a --- /dev/null +++ b/tasks/vinyaikina_e_vert_ribbon_scheme/mpi/include/ops_mpi.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "task/include/task.hpp" +#include "vinyaikina_e_vert_ribbon_scheme/common/include/common.hpp" + +namespace vinyaikina_e_vert_ribbon_scheme { + +class VinyaikinaEVertRibbonSchemeMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit VinyaikinaEVertRibbonSchemeMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + int rows_ = 0; + int cols_ = 0; + std::vector matrix_; + std::vector vector_; + std::vector result_; +}; + +} // namespace vinyaikina_e_vert_ribbon_scheme diff --git a/tasks/vinyaikina_e_vert_ribbon_scheme/mpi/src/ops_mpi.cpp b/tasks/vinyaikina_e_vert_ribbon_scheme/mpi/src/ops_mpi.cpp new file mode 100644 index 00000000..6a49d817 --- /dev/null +++ b/tasks/vinyaikina_e_vert_ribbon_scheme/mpi/src/ops_mpi.cpp @@ -0,0 +1,145 @@ +#include "vinyaikina_e_vert_ribbon_scheme/mpi/include/ops_mpi.hpp" + +#include + +#include +#include + +#include "vinyaikina_e_vert_ribbon_scheme/common/include/common.hpp" + +namespace vinyaikina_e_vert_ribbon_scheme { + +namespace { + +void ComputeColumnDistribution(int cols, int num_proc, std::vector &counts, std::vector &starts) { + int base = cols / num_proc; + int rem = cols % num_proc; + for (int i = 0; i < num_proc; i++) { + counts[i] = base + (i < rem ? 1 : 0); + } + starts[0] = 0; + for (int i = 1; i < num_proc; i++) { + starts[i] = starts[i - 1] + counts[i - 1]; + } +} + +std::vector PackMatrixBuffer(int rows, int cols, int num_proc, const std::vector &matrix, + const std::vector &col_counts, const std::vector &col_starts, + std::vector &send_counts, std::vector &send_offsets) { + int total_send = 0; + for (int pr = 0; pr < num_proc; pr++) { + send_counts[pr] = rows * col_counts[pr]; + send_offsets[pr] = total_send; + total_send += send_counts[pr]; + } + std::vector buf(total_send); + for (int pr = 0; pr < num_proc; pr++) { + for (int i = 0; i < rows; i++) { + for (int j = 0; j < col_counts[pr]; j++) { + buf[send_offsets[pr] + (i * col_counts[pr]) + j] = matrix[(i * cols) + col_starts[pr] + j]; + } + } + } + return buf; +} + +} // namespace + +VinyaikinaEVertRibbonSchemeMPI::VinyaikinaEVertRibbonSchemeMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool VinyaikinaEVertRibbonSchemeMPI::ValidationImpl() { + return GetInput() > 0 && GetOutput() == 0; +} + +bool VinyaikinaEVertRibbonSchemeMPI::PreProcessingImpl() { + rows_ = GetInput(); + cols_ = GetInput(); + if (rows_ <= 0 || cols_ <= 0) { + return false; + } + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (rank == 0) { + matrix_.assign(static_cast(rows_) * cols_, 1); + vector_.assign(cols_, 1); + } + result_.assign(rows_, 0); + return true; +} + +bool VinyaikinaEVertRibbonSchemeMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + std::vector col_counts(size); + std::vector col_starts(size); + ComputeColumnDistribution(cols_, size, col_counts, col_starts); + + int my_cols = col_counts[rank]; + + std::vector matrix_send_buf; + std::vector matrix_send_counts(size, 0); + std::vector matrix_send_offsets(size, 0); + + if (rank == 0) { + matrix_send_buf = + PackMatrixBuffer(rows_, cols_, size, matrix_, col_counts, col_starts, matrix_send_counts, matrix_send_offsets); + } + + int matrix_recv_count = rows_ * my_cols; + std::vector local_matrix(matrix_recv_count); + MPI_Scatterv(matrix_send_buf.data(), matrix_send_counts.data(), matrix_send_offsets.data(), MPI_INT, + local_matrix.data(), matrix_recv_count, MPI_INT, 0, MPI_COMM_WORLD); + + std::vector vector_send_counts(size, 0); + std::vector vector_send_offsets(size, 0); + for (int pr = 0; pr < size; pr++) { + vector_send_counts[pr] = col_counts[pr]; + vector_send_offsets[pr] = col_starts[pr]; + } + + std::vector local_vector(my_cols); + MPI_Scatterv(vector_.data(), vector_send_counts.data(), vector_send_offsets.data(), MPI_INT, local_vector.data(), + my_cols, MPI_INT, 0, MPI_COMM_WORLD); + + std::vector local_result(rows_, 0); + for (int i = 0; i < rows_; i++) { + for (int j = 0; j < my_cols; j++) { + local_result[i] += local_matrix[(i * my_cols) + j] * local_vector[j]; + } + } + + result_.assign(rows_, 0); + MPI_Reduce(local_result.data(), result_.data(), rows_, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); + + int total = 0; + if (rank == 0) { + for (int i = 0; i < rows_; i++) { + total += result_[i]; + } + for (int dest = 1; dest < size; dest++) { + MPI_Send(&total, 1, MPI_INT, dest, 0, MPI_COMM_WORLD); + } + } else { + MPI_Recv(&total, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } + + GetOutput() = total; + return true; +} + +bool VinyaikinaEVertRibbonSchemeMPI::PostProcessingImpl() { + if (GetInput() == 0) { + return false; + } + GetOutput() /= GetInput(); + return true; +} + +} // namespace vinyaikina_e_vert_ribbon_scheme diff --git a/tasks/vinyaikina_e_vert_ribbon_scheme/report.md b/tasks/vinyaikina_e_vert_ribbon_scheme/report.md new file mode 100644 index 00000000..fb7277f9 --- /dev/null +++ b/tasks/vinyaikina_e_vert_ribbon_scheme/report.md @@ -0,0 +1,553 @@ +# Умножение матрицы на вектор (Вертикальная ленточная схема) + +- Студент: Виняйкина Екатерина Александровна +- Группа: 3823Б1ПР3 +- Технология: SEQ, MPI +- Вариант: 12 + +## 1. Введение + +Умножение матрицы на вектор - одна из фундаментальных операций + линейной алгебры, широко используемая в научных вычислениях, + машинном обучении и инженерных приложениях. При работе с большими + матрицами последовательные алгоритмы становятся узким местом + производительности. Параллелизация с использованием технологии MPI + позволяет распределить вычисления между несколькими процессами + и значительно ускорить обработку данных. + +Целью данной лабораторной работы является реализация последовательного + и параллельного алгоритмов умножения матрицы на вектор + с использованием вертикальной ленточной схемы распределения данных + между процессами. Ожидаемый результат - корректно работающие + алгоритмы с улучшенной производительностью при увеличении + числа процессов. + +## 2. Постановка задачи + +Задача заключается в умножении квадратной матрицы размером n×n + на вектор длины n, где все элементы матрицы и вектора равны единице. + Результатом работы алгоритма является сумма всех элементов + результирующего вектора, деленная на размер матрицы n. + +Формальное определение: + +- Входные данные: целое число n > 0 (размер матрицы и вектора) +- Матрица A: квадратная матрица n×n, где все элементы `a[i][j]` = 1 +- Вектор x: вектор длины n, где все элементы `x[j]` = 1 +- Результат: вектор y = A × x, + где `y[i]` = сумма по j от 0 до n-1 (`a[i][j]` × `x[j]`) +- Выходные данные: целое число, + равное (сумма всех элементов y) / n = (n × n) / n = n + +Ограничения: + +- Матрица квадратная (количество строк равно количеству столбцов) +- Все элементы матрицы и вектора равны 1 +- Размер матрицы должен быть положительным числом +- Для параллельного алгоритма используется вертикальная ленточная + схема распределения данных + +## 3. Последовательный алгоритм + +Последовательный алгоритм выполняет вычисления на одном процессе + без использования параллелизма. + +Алгоритм состоит из следующих этапов: + +1. Инициализация: создается квадратная матрица размером n×n, + все элементы которой равны 1, и вектор длины n, + все элементы которого равны 1. + +2. Умножение матрицы на вектор: для каждой строки матрицы + вычисляется скалярное произведение строки на вектор: + - Для строки i (от 0 до n-1): + - Инициализируется переменная sum = 0 + - Для каждого столбца j (от 0 до n-1): + - sum += `matrix[i][j]` × `vector[j]` + - Сохраняется `result[i]` = sum + +3. Вычисление общей суммы: суммируются все значения + из результирующего вектора result. + +4. Нормализация результата: общая сумма делится на размер матрицы n. + +Временная сложность алгоритма: O(n²), так как требуется выполнить n² + операций умножения и сложения. + +Пространственная сложность: O(n²) для хранения матрицы, + O(n) для хранения вектора и результирующего вектора. + +## 4. Схема распараллеливания + +Для параллельного алгоритма используется вертикальная ленточная + схема распределения данных (vertical ribbon scheme). + Матрица и вектор делятся между процессами по столбцам. + +### 4.1 Распределение данных + +При использовании p процессов столбцы матрицы и соответствующие + элементы вектора распределяются следующим образом: + +- Каждый процесс получает примерно равное количество столбцов +- Если n не делится нацело на p, то первые (n mod p) процессов + получают на один столбец больше +- Процесс с рангом rank получает столбцы с индексами + от col_starts\[rank\] до col_starts\[rank\] + col_counts\[rank\] - 1 +- Каждый процесс получает соответствующую часть вектора + (элементы с теми же индексами, что и столбцы матрицы) + +### 4.2 Коммуникационная схема + +Алгоритм использует следующие операции MPI: + +1. MPI_Scatterv для матрицы: процесс с рангом 0 упаковывает + данные матрицы для каждого процесса в отдельный буфер + и распределяет их. Каждый процесс получает свои столбцы матрицы + в виде непрерывного массива данных. + +2. MPI_Scatterv для вектора: процесс с рангом 0 распределяет + соответствующие элементы вектора каждому процессу. + +3. Локальные вычисления: каждый процесс вычисляет частичные + результаты умножения для своих столбцов. Для каждой строки + процесс умножает свои столбцы матрицы на соответствующие элементы + вектора и суммирует результаты. + +4. MPI_Reduce: все процессы отправляют свои частичные результаты + процессу с рангом 0, который выполняет операцию суммирования + (MPI_SUM) для получения полного результирующего вектора. + +5. MPI_Send/MPI_Recv: процесс с рангом 0 вычисляет общую сумму + всех элементов результирующего вектора и отправляет результат + остальным процессам для обеспечения согласованности данных. + +### 4.3 Псевдокод параллельного алгоритма + +```text +1. Инициализация MPI +2. Получить rank и size +3. Если rank == 0: + Создать матрицу n×n, заполнить единицами + Создать вектор длины n, заполнить единицами +4. Вычислить распределение столбцов между процессами +5. Если rank == 0: + Упаковать данные матрицы для каждого процесса в matrix_send_buf +6. MPI_Scatterv: распределить столбцы матрицы между процессами +7. MPI_Scatterv: распределить соответствующие элементы вектора +8. Для каждой строки i: + Для каждого столбца j в локальной части: + local_result[i] += local_matrix[i][j] × local_vector[j] +9. MPI_Reduce: собрать частичные результаты на процесс 0 +10. Если rank == 0: + Вычислить total = сумма всех элементов result + Отправить total остальным процессам + Иначе: + Получить total от процесса 0 +11. Результат = total / n +``` + +## 5. Детали реализации + +### 5.1 Структура кода + +Проект состоит из следующих файлов: + +- `seq/include/ops_seq.hpp` - заголовочный файл для последовательной + реализации +- `seq/src/ops_seq.cpp` - реализация последовательного алгоритма +- `mpi/include/ops_mpi.hpp` - заголовочный файл для MPI реализации +- `mpi/src/ops_mpi.cpp` - реализация параллельного алгоритма +- `common/include/common.hpp` - общие определения типов данных +- `tests/functional/main.cpp` - функциональные тесты +- `tests/performance/main.cpp` - тесты производительности + +### 5.2 Ключевые классы и функции + +Класс `VinyaikinaEVertRibbonSchemeSEQ` реализует последовательный + алгоритм: + +- `ValidationImpl()` - проверка корректности входных данных +- `PreProcessingImpl()` - создание матрицы и вектора +- `RunImpl()` - умножение матрицы на вектор +- `PostProcessingImpl()` - нормализация результата + +Класс `VinyaikinaEVertRibbonSchemeMPI` реализует параллельный + алгоритм с аналогичными методами, но с использованием MPI + операций в `RunImpl()`. + +Вспомогательные функции в анонимном namespace: + +- `ComputeColumnDistribution()` - вычисление распределения + столбцов между процессами +- `PackMatrixBuffer()` - упаковка данных матрицы для MPI_Scatterv + +### 5.3 Важные предположения и граничные случаи + +- Размер матрицы должен быть положительным числом + (проверяется в ValidationImpl) +- Матрица всегда квадратная +- Все элементы матрицы и вектора равны 1 +- Если число процессов больше размера матрицы, + некоторые процессы получат 0 столбцов +- Процесс с рангом 0 отвечает за создание исходной матрицы + и вектора и координацию коммуникаций + +### 5.4 Использование памяти + +Последовательный алгоритм: + +- Матрица: O(n²) памяти +- Вектор: O(n) памяти +- Результирующий вектор: O(n) памяти +- Общее использование: O(n²) + +Параллельный алгоритм: + +- Процесс 0: матрица O(n²) + вектор O(n) + +- буфер для рассылки матрицы O(n²) = O(n²) + +- Остальные процессы: локальные данные матрицы O(n²/p) + +- локальный вектор O(n/p) + массивы результатов O(n) = O(n²/p) + +- Общее использование на всех процессах: O(n² + p×n) + +## 6. Экспериментальная установка + +### 6.1 Оборудование и программное обеспечение + +- Процессор: Intel Core i7-14700KF +- Количество ядер/потоков: 20 ядер / 28 потоков +- Оперативная память: 16 гб +- Операционная система: Windows 11 + +### 6.2 Инструментарий + +- Компилятор: MSVC 14.44 +- Тип сборки: Release +- Версия MPI: Microsoft MPI 10.1 + +### 6.3 Переменные окружения + +- PPC_NUM_PROC: количество процессов для запуска тестов +- Размер тестовых данных: n = 4000 + +### 6.4 Тестовые данные + +Тестовые данные генерируются программно. Для функциональных тестов + используются матрицы размером от 1×1 до 100×100. + Для тестов производительности используется матрица размером + 4000×4000 и вектор длины 4000. + +## 7. Результаты и обсуждение + +### 7.1 Проверка корректности + +Корректность алгоритмов проверяется с помощью набора функциональных + тестов, покрывающих различные размеры матриц + (1, 2, 3, 5, 7, 10, 50, 100) и граничные случаи + (отрицательные значения, нулевой размер). Все тесты проверяют, + что результат работы алгоритма равен входному значению n, + что соответствует ожидаемому результату (n×n)/n = n. + +Дополнительно проверяется корректность работы MPI алгоритма + при различном количестве процессов (от 1 до 30). + Результаты последовательного и параллельного алгоритмов совпадают + для всех тестовых случаев. + +### 7.2 Производительность + +Результаты измерений времени выполнения представлены + в таблицах ниже. Измерения проводились для матрицы размером + 4000×4000 и вектора длины 4000 на различном количестве процессов. + +#### Таблица 1: Результаты task_run + +| Процессы | Время, с | Ускорение | Эффективность | +|----------|----------|-----------|---------------| +| SEQ (1) | 0.0289 | 1.00 | N/A | +| MPI (2) | 0.1575 | 0.18 | 9.2% | +| MPI (4) | 0.1463 | 0.20 | 4.9% | +| MPI (6) | 0.1428 | 0.20 | 3.4% | +| MPI (8) | 0.1411 | 0.20 | 2.6% | + +#### Таблица 2: Результаты task_pipeline + +| Процессы | Время, с | Ускорение | Эффективность | +|----------|----------|-----------|---------------| +| SEQ (1) | 0.0502 | 1.00 | N/A | +| MPI (2) | 0.1786 | 0.28 | 14.1% | +| MPI (4) | 0.1677 | 0.30 | 7.5% | +| MPI (6) | 0.1638 | 0.31 | 5.1% | +| MPI (8) | 0.1634 | 0.31 | 3.8% | + +### 7.3 Анализ результатов + +Результаты измерений показывают, что параллельный алгоритм + работает медленнее последовательного. Это объясняется значительными + накладными расходами на коммуникации между процессами при работе + с матрицей размером 4000×4000. + +Для task_run наблюдается следующая картина: + +- При использовании 2 процессов время выполнения увеличивается + в 5.5 раз по сравнению с последовательным алгоритмом +- При увеличении числа процессов до 4, 6 и 8 время выполнения + немного улучшается, но остается значительно хуже + последовательного варианта +- Эффективность параллельного алгоритма очень низкая + (от 2.6% до 9.2%), что указывает на доминирование + коммуникационных накладных расходов над вычислительной работой + +Для task_pipeline ситуация аналогична: + +- Параллельный алгоритм работает в 3.2-3.6 раза медленнее + последовательного +- Эффективность также низкая (от 3.8% до 14.1%) +- Task_run действительно быстрее task_pipeline, так как не включает + время на валидацию и предобработку данных + +Основными факторами, влияющими на производительность, являются: + +1. Время на коммуникации (MPI_Scatterv для матрицы и вектора, + MPI_Reduce, MPI_Send/Recv) - составляет значительную долю + общего времени выполнения +2. Накладные расходы на упаковку данных на процессе 0 + перед MPI_Scatterv - требуется создание дополнительного + буфера размером O(n²) для матрицы +3. Неравномерность распределения нагрузки - при небольшом + количестве процессов накладные расходы на коммуникации + превышают выигрыш от параллельных вычислений +4. Размер задачи может быть недостаточным для демонстрации + преимуществ параллелизма - для матрицы 4000×4000 вычислительная + работа относительно невелика по сравнению с объемом + передаваемых данных + +Вывод: для данной задачи и размера данных последовательный алгоритм + оказывается более эффективным. Параллелизация становится выгодной + только при работе с существенно большими матрицами, когда + вычислительная работа значительно превышает коммуникационные + накладные расходы. + +## 8. Выводы + +В ходе выполнения лабораторной работы были успешно реализованы + последовательный и параллельный алгоритмы умножения матрицы + на вектор с использованием вертикальной ленточной схемы + распределения данных. + +Последовательный алгоритм показал отличную производительность + для матрицы размером 4000×4000, выполняясь за 0.0289 секунды + (task_run) и 0.0502 секунды (task_pipeline). Алгоритм прост + в реализации и обеспечивает корректность результатов. + +Параллельный алгоритм с использованием MPI был успешно реализован + с применением вертикальной ленточной схемы распределения данных. + Однако экспериментальные результаты показали, что для данного + размера задачи параллелизация не дает преимущества + по производительности. Параллельный алгоритм работает в 3-5 раз + медленнее последовательного из-за значительных накладных расходов + на коммуникации. + +Основными причинами низкой эффективности параллельного + алгоритма являются: + +- Значительные накладные расходы на коммуникации + (MPI_Scatterv для матрицы и вектора, MPI_Reduce, + MPI_Send/Recv) превышают выигрыш от параллельных вычислений +- Необходимость упаковки данных на процессе 0 перед рассылкой + создает дополнительную нагрузку +- Необходимость синхронизации процессов добавляет задержки + +Выводы: + +1. Для матриц размером 4000×4000 последовательный алгоритм + является оптимальным выбором +2. Параллелизация становится выгодной только при работе + с существенно большими матрицами (порядка 100000×100000 + и более), когда вычислительная работа значительно превышает + коммуникационные накладные расходы +3. Вертикальная ленточная схема корректно реализована + и обеспечивает равномерное распределение данных + между процессами +4. Оба алгоритма прошли все функциональные тесты + и обеспечивают корректность результатов + +## 9. Источники + +1. Гергель В.П. Теория и практика параллельных вычислений: + Учебное пособие. - М.: Интернет-Университет Информационных + Технологий, 2007. - 424 с. + +2. Сысоев А.В., лекции по курсу "Параллельное программирование" - + ННГУ, 2025 год. + +3. Message Passing Interface Forum. MPI: A Message-Passing + Interface Standard, Version 4.0. - 2021. + URL: + +4. Антонов А.С. Параллельное программирование с использованием + технологии MPI: Учебное пособие. - М.: Издательство МГУ, +2013. - 80 с. + +5. Gropp W., Lusk E., Skjellum A. Using MPI: Portable Parallel + Programming with the Message-Passing Interface. + 3rd Edition. - MIT Press, 2014. - 392 p. + +## 10. Приложение + +### 10.1 Последовательный алгоритм (RunImpl) + +Основная функция умножения матрицы на вектор + в последовательной реализации: + +```cpp +bool VinyaikinaEVertRibbonSchemeSEQ::RunImpl() { + for (int i = 0; i < rows_; i++) { + int sum = 0; + for (int j = 0; j < cols_; j++) { + sum += matrix_[(i * cols_) + j] * vector_[j]; + } + result_[i] = sum; + } + GetOutput() = 0; + for (int i = 0; i < rows_; i++) { + GetOutput() += result_[i]; + } + return true; +} +``` + +### 10.2 Вспомогательные функции для параллельного алгоритма + +Функция вычисления распределения столбцов между процессами: + +```cpp +void ComputeColumnDistribution(int cols, int num_proc, + std::vector &counts, + std::vector &starts) { + int base = cols / num_proc; + int rem = cols % num_proc; + for (int i = 0; i < num_proc; i++) { + counts[i] = base + (i < rem ? 1 : 0); + } + starts[0] = 0; + for (int i = 1; i < num_proc; i++) { + starts[i] = starts[i - 1] + counts[i - 1]; + } +} +``` + +Функция упаковки данных матрицы для MPI_Scatterv: + +```cpp +std::vector PackMatrixBuffer(int rows, int cols, + int num_proc, + const std::vector &matrix, + const std::vector &col_counts, + const std::vector &col_starts, + std::vector &send_counts, + std::vector &send_offsets) { + int total_send = 0; + for (int pr = 0; pr < num_proc; pr++) { + send_counts[pr] = rows * col_counts[pr]; + send_offsets[pr] = total_send; + total_send += send_counts[pr]; + } + std::vector buf(total_send); + for (int pr = 0; pr < num_proc; pr++) { + for (int i = 0; i < rows; i++) { + for (int j = 0; j < col_counts[pr]; j++) { + buf[send_offsets[pr] + (i * col_counts[pr]) + j] = + matrix[(i * cols) + col_starts[pr] + j]; + } + } + } + return buf; +} +``` + +### 10.3 Параллельный алгоритм (RunImpl) + +Основная функция параллельного алгоритма с использованием MPI: + +```cpp +bool VinyaikinaEVertRibbonSchemeMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + std::vector col_counts(size); + std::vector col_starts(size); + ComputeColumnDistribution(cols_, size, col_counts, col_starts); + + int my_cols = col_counts[rank]; + + std::vector matrix_send_buf; + std::vector matrix_send_counts(size, 0); + std::vector matrix_send_offsets(size, 0); + + if (rank == 0) { + matrix_send_buf = PackMatrixBuffer( + rows_, cols_, size, matrix_, + col_counts, col_starts, + matrix_send_counts, matrix_send_offsets); + } + + int matrix_recv_count = rows_ * my_cols; + std::vector local_matrix(matrix_recv_count); + MPI_Scatterv(matrix_send_buf.data(), + matrix_send_counts.data(), + matrix_send_offsets.data(), MPI_INT, + local_matrix.data(), + matrix_recv_count, MPI_INT, + 0, MPI_COMM_WORLD); + + std::vector vector_send_counts(size, 0); + std::vector vector_send_offsets(size, 0); + for (int pr = 0; pr < size; pr++) { + vector_send_counts[pr] = col_counts[pr]; + vector_send_offsets[pr] = col_starts[pr]; + } + + std::vector local_vector(my_cols); + MPI_Scatterv(vector_.data(), + vector_send_counts.data(), + vector_send_offsets.data(), MPI_INT, + local_vector.data(), + my_cols, MPI_INT, + 0, MPI_COMM_WORLD); + + std::vector local_result(rows_, 0); + for (int i = 0; i < rows_; i++) { + for (int j = 0; j < my_cols; j++) { + local_result[i] += + local_matrix[(i * my_cols) + j] * local_vector[j]; + } + } + + result_.assign(rows_, 0); + MPI_Reduce(local_result.data(), result_.data(), + rows_, MPI_INT, MPI_SUM, + 0, MPI_COMM_WORLD); + + int total = 0; + if (rank == 0) { + for (int i = 0; i < rows_; i++) { + total += result_[i]; + } + for (int dest = 1; dest < size; dest++) { + MPI_Send(&total, 1, MPI_INT, dest, + 0, MPI_COMM_WORLD); + } + } else { + MPI_Recv(&total, 1, MPI_INT, 0, 0, + MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } + + GetOutput() = total; + return true; +} +``` diff --git a/tasks/vinyaikina_e_vert_ribbon_scheme/seq/include/ops_seq.hpp b/tasks/vinyaikina_e_vert_ribbon_scheme/seq/include/ops_seq.hpp new file mode 100644 index 00000000..f91e8e6f --- /dev/null +++ b/tasks/vinyaikina_e_vert_ribbon_scheme/seq/include/ops_seq.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "task/include/task.hpp" +#include "vinyaikina_e_vert_ribbon_scheme/common/include/common.hpp" + +namespace vinyaikina_e_vert_ribbon_scheme { + +class VinyaikinaEVertRibbonSchemeSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit VinyaikinaEVertRibbonSchemeSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + int rows_ = 0; + int cols_ = 0; + std::vector matrix_; + std::vector vector_; + std::vector result_; +}; + +} // namespace vinyaikina_e_vert_ribbon_scheme diff --git a/tasks/vinyaikina_e_vert_ribbon_scheme/seq/src/ops_seq.cpp b/tasks/vinyaikina_e_vert_ribbon_scheme/seq/src/ops_seq.cpp new file mode 100644 index 00000000..3e08256e --- /dev/null +++ b/tasks/vinyaikina_e_vert_ribbon_scheme/seq/src/ops_seq.cpp @@ -0,0 +1,55 @@ +#include "vinyaikina_e_vert_ribbon_scheme/seq/include/ops_seq.hpp" + +#include +#include + +#include "vinyaikina_e_vert_ribbon_scheme/common/include/common.hpp" + +namespace vinyaikina_e_vert_ribbon_scheme { + +VinyaikinaEVertRibbonSchemeSEQ::VinyaikinaEVertRibbonSchemeSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool VinyaikinaEVertRibbonSchemeSEQ::ValidationImpl() { + return GetInput() > 0 && GetOutput() == 0; +} + +bool VinyaikinaEVertRibbonSchemeSEQ::PreProcessingImpl() { + rows_ = GetInput(); + cols_ = GetInput(); + if (rows_ <= 0 || cols_ <= 0) { + return false; + } + matrix_.assign(static_cast(rows_) * cols_, 1); + vector_.assign(cols_, 1); + result_.assign(rows_, 0); + return true; +} + +bool VinyaikinaEVertRibbonSchemeSEQ::RunImpl() { + for (int i = 0; i < rows_; i++) { + int sum = 0; + for (int j = 0; j < cols_; j++) { + sum += matrix_[(i * cols_) + j] * vector_[j]; + } + result_[i] = sum; + } + GetOutput() = 0; + for (int i = 0; i < rows_; i++) { + GetOutput() += result_[i]; + } + return true; +} + +bool VinyaikinaEVertRibbonSchemeSEQ::PostProcessingImpl() { + if (GetInput() == 0) { + return false; + } + GetOutput() /= GetInput(); + return true; +} + +} // namespace vinyaikina_e_vert_ribbon_scheme diff --git a/tasks/vinyaikina_e_vert_ribbon_scheme/settings.json b/tasks/vinyaikina_e_vert_ribbon_scheme/settings.json new file mode 100644 index 00000000..16f25e42 --- /dev/null +++ b/tasks/vinyaikina_e_vert_ribbon_scheme/settings.json @@ -0,0 +1,7 @@ +{ + "tasks": { + "mpi": "enabled", + "seq": "enabled" + }, + "tasks_type": "processes" +} diff --git a/tasks/vinyaikina_e_vert_ribbon_scheme/tests/.clang-tidy b/tasks/vinyaikina_e_vert_ribbon_scheme/tests/.clang-tidy new file mode 100644 index 00000000..ef43b7aa --- /dev/null +++ b/tasks/vinyaikina_e_vert_ribbon_scheme/tests/.clang-tidy @@ -0,0 +1,13 @@ +InheritParentConfig: true + +Checks: > + -modernize-loop-convert, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-avoid-non-const-global-variables, + -misc-use-anonymous-namespace, + -modernize-use-std-print, + -modernize-type-traits + +CheckOptions: + - key: readability-function-cognitive-complexity.Threshold + value: 50 # Relaxed for tests diff --git a/tasks/vinyaikina_e_vert_ribbon_scheme/tests/functional/main.cpp b/tasks/vinyaikina_e_vert_ribbon_scheme/tests/functional/main.cpp new file mode 100644 index 00000000..2abf9ea2 --- /dev/null +++ b/tasks/vinyaikina_e_vert_ribbon_scheme/tests/functional/main.cpp @@ -0,0 +1,98 @@ +#include + +#include +#include +#include +#include + +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" +#include "vinyaikina_e_vert_ribbon_scheme/common/include/common.hpp" +#include "vinyaikina_e_vert_ribbon_scheme/mpi/include/ops_mpi.hpp" +#include "vinyaikina_e_vert_ribbon_scheme/seq/include/ops_seq.hpp" + +namespace vinyaikina_e_vert_ribbon_scheme { + +class VinyaikinaEVertRibbonSchemeFuncTests : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + return std::to_string(std::get<0>(test_param)) + "_" + std::get<1>(test_param); + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + input_data_ = std::get<0>(params); + } + + bool CheckTestOutputData(OutType &output_data) final { + return (input_data_ == output_data); + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_ = 0; +}; + +namespace { + +TEST_P(VinyaikinaEVertRibbonSchemeFuncTests, MatmulFromPic) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = {std::make_tuple(3, "3"), std::make_tuple(5, "5"), std::make_tuple(7, "7")}; + +const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_vinyaikina_e_vert_ribbon_scheme), + ppc::util::AddFuncTask( + kTestParam, PPC_SETTINGS_vinyaikina_e_vert_ribbon_scheme)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = + VinyaikinaEVertRibbonSchemeFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(PicMatrixTests, VinyaikinaEVertRibbonSchemeFuncTests, kGtestValues, kPerfTestName); + +} // namespace + +namespace { + +const std::array kEdgeParams = {std::make_tuple(1, "one"), std::make_tuple(2, "two"), + std::make_tuple(10, "ten"), std::make_tuple(50, "fifty"), + std::make_tuple(100, "hundred")}; + +const auto kEdgeTasksList = std::tuple_cat(ppc::util::AddFuncTask( + kEdgeParams, PPC_SETTINGS_vinyaikina_e_vert_ribbon_scheme), + ppc::util::AddFuncTask( + kEdgeParams, PPC_SETTINGS_vinyaikina_e_vert_ribbon_scheme)); + +const auto kEdgeValues = ppc::util::ExpandToValues(kEdgeTasksList); + +const auto kEdgeTestName = + VinyaikinaEVertRibbonSchemeFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(EdgeCaseTests, VinyaikinaEVertRibbonSchemeFuncTests, kEdgeValues, kEdgeTestName); + +TEST(VinyaikinaEVertRibbonSchemeSeqValidation, RejectsZeroInput) { + VinyaikinaEVertRibbonSchemeSEQ task(0); + EXPECT_FALSE(task.Validation()); + task.PreProcessing(); + task.Run(); + task.PostProcessing(); +} + +TEST(VinyaikinaEVertRibbonSchemeSeqValidation, RejectsNegativeInput) { + VinyaikinaEVertRibbonSchemeSEQ task(-3); + EXPECT_FALSE(task.Validation()); + task.PreProcessing(); + task.Run(); + task.PostProcessing(); +} + +} // namespace + +} // namespace vinyaikina_e_vert_ribbon_scheme diff --git a/tasks/vinyaikina_e_vert_ribbon_scheme/tests/performance/main.cpp b/tasks/vinyaikina_e_vert_ribbon_scheme/tests/performance/main.cpp new file mode 100644 index 00000000..1f4583e5 --- /dev/null +++ b/tasks/vinyaikina_e_vert_ribbon_scheme/tests/performance/main.cpp @@ -0,0 +1,41 @@ +#include + +#include "util/include/perf_test_util.hpp" +#include "vinyaikina_e_vert_ribbon_scheme/common/include/common.hpp" +#include "vinyaikina_e_vert_ribbon_scheme/mpi/include/ops_mpi.hpp" +#include "vinyaikina_e_vert_ribbon_scheme/seq/include/ops_seq.hpp" + +namespace vinyaikina_e_vert_ribbon_scheme { + +class VinyaikinaEVertRibbonSchemePerfTests : public ppc::util::BaseRunPerfTests { + const int kCount_ = 4000; + InType input_data_{}; + + void SetUp() override { + input_data_ = kCount_; + } + + bool CheckTestOutputData(OutType &output_data) final { + return input_data_ == output_data; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(VinyaikinaEVertRibbonSchemePerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_vinyaikina_e_vert_ribbon_scheme); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = VinyaikinaEVertRibbonSchemePerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, VinyaikinaEVertRibbonSchemePerfTests, kGtestValues, kPerfTestName); + +} // namespace vinyaikina_e_vert_ribbon_scheme