diff --git a/tasks/fatehov_k_matrix_crs/common/include/common.hpp b/tasks/fatehov_k_matrix_crs/common/include/common.hpp new file mode 100644 index 0000000000..453dc456c9 --- /dev/null +++ b/tasks/fatehov_k_matrix_crs/common/include/common.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace fatehov_k_matrix_crs { + +using InType = std::tuple, std::vector, std::vector, + std::vector, std::vector, std::vector>; +using OutType = std::vector; +using TestType = std::tuple, std::vector, std::vector, + std::vector, std::vector, std::vector, std::vector>; +using BaseTask = ppc::task::Task; + +} // namespace fatehov_k_matrix_crs diff --git a/tasks/fatehov_k_matrix_crs/info.json b/tasks/fatehov_k_matrix_crs/info.json new file mode 100644 index 0000000000..ca5a30c337 --- /dev/null +++ b/tasks/fatehov_k_matrix_crs/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Камиль", + "last_name": "Фатехов", + "middle_name": "Гаярович", + "group_number": "3823Б1ФИ3", + "task_number": "3" + } +} diff --git a/tasks/fatehov_k_matrix_crs/mpi/include/ops_mpi.hpp b/tasks/fatehov_k_matrix_crs/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..d6b05373ad --- /dev/null +++ b/tasks/fatehov_k_matrix_crs/mpi/include/ops_mpi.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "fatehov_k_matrix_crs/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace fatehov_k_matrix_crs { + +class FatehovKMatrixCRSMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit FatehovKMatrixCRSMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + + static const int kMaxRows = 10000; + static const int kMaxCols = 10000; + static const int kMaxNonZero = 10000000; +}; + +} // namespace fatehov_k_matrix_crs diff --git a/tasks/fatehov_k_matrix_crs/mpi/src/ops_mpi.cpp b/tasks/fatehov_k_matrix_crs/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..c73a488e5a --- /dev/null +++ b/tasks/fatehov_k_matrix_crs/mpi/src/ops_mpi.cpp @@ -0,0 +1,259 @@ +#include "fatehov_k_matrix_crs/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "fatehov_k_matrix_crs/common/include/common.hpp" + +namespace fatehov_k_matrix_crs { + +namespace { + +// Вспомогательная функция для вычисления границ строк для конкретного процесса +std::pair GetProcessRowRange(int target_rank, int size, int rows_total) { + int rows_per_proc = rows_total / size; + int rem = rows_total % size; + int start = 0; + for (int j = 0; j < target_rank; ++j) { + start += (rows_per_proc + (j < rem ? 1 : 0)); + } + int end = start + (rows_per_proc + (target_rank < rem ? 1 : 0)); + return {start, std::min(end, rows_total)}; +} + +void BroadcastMatrixSizes(size_t &rows, size_t &cols, int rank, const InType &input) { + if (rank == 0) { + rows = std::get<0>(input); + cols = std::get<1>(input); + } + MPI_Bcast(&rows, 1, MPI_UNSIGNED_LONG_LONG, 0, MPI_COMM_WORLD); + MPI_Bcast(&cols, 1, MPI_UNSIGNED_LONG_LONG, 0, MPI_COMM_WORLD); +} + +void BroadcastMatrixB(std::vector &val_b, std::vector &col_b, std::vector &ptr_b, size_t &nnz_b, + size_t rows, int rank, const InType &input) { + if (rank == 0) { + val_b = std::get<3>(input); + col_b = std::get<5>(input); + ptr_b = std::get<7>(input); + nnz_b = val_b.size(); + } + MPI_Bcast(&nnz_b, 1, MPI_UNSIGNED_LONG_LONG, 0, MPI_COMM_WORLD); + + if (rank != 0) { + val_b.resize(nnz_b); + col_b.resize(nnz_b); + } + + if (nnz_b > 0) { + MPI_Bcast(val_b.data(), static_cast(nnz_b), MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(static_cast(col_b.data()), static_cast(nnz_b), MPI_UNSIGNED_LONG_LONG, 0, MPI_COMM_WORLD); + } + + ptr_b.resize(rows + 1); + MPI_Bcast(static_cast(ptr_b.data()), static_cast(rows) + 1, MPI_UNSIGNED_LONG_LONG, 0, MPI_COMM_WORLD); +} + +void BroadcastMatrixAStructure(std::vector &ptr_a, size_t rows, int rank, const InType &input) { + ptr_a.resize(rows + 1); + if (rank == 0) { + ptr_a = std::get<6>(input); + } + MPI_Bcast(static_cast(ptr_a.data()), static_cast(rows) + 1, MPI_UNSIGNED_LONG_LONG, 0, MPI_COMM_WORLD); +} + +void DistributeLocalWork(int &local_rows, int &start_row, int &end_row, size_t rows, int size, int rank) { + auto range = GetProcessRowRange(rank, size, static_cast(rows)); + start_row = range.first; + end_row = range.second; + local_rows = end_row - start_row; +} + +// Логика отправки данных от корня (rank 0) другим процессам +void SendMatrixAParts(int size, const std::vector &ptr_a, const InType &input) { + const auto &values_a = std::get<2>(input); + const auto &cols_a = std::get<4>(input); + int rows_total = static_cast(ptr_a.size() - 1); + + for (int i = 1; i < size; ++i) { + auto range = GetProcessRowRange(i, size, rows_total); + size_t sz = ptr_a[range.second] - ptr_a[range.first]; + if (sz > 0) { + MPI_Send(&values_a[ptr_a[range.first]], static_cast(sz), MPI_DOUBLE, i, 0, MPI_COMM_WORLD); + MPI_Send(static_cast(&cols_a[ptr_a[range.first]]), static_cast(sz), MPI_UNSIGNED_LONG_LONG, i, + 1, MPI_COMM_WORLD); + } + } +} + +void ScatterMatrixA(std::vector &val_a_loc, std::vector &col_a_loc, const std::vector &ptr_a, + int start_row, int end_row, int rank, int size, const InType &input) { + if (start_row < 0 || std::cmp_greater(end_row, ptr_a.size() - 1)) { + val_a_loc.clear(); + col_a_loc.clear(); + return; + } + + size_t local_nnz = ptr_a[end_row] - ptr_a[start_row]; + val_a_loc.resize(local_nnz); + col_a_loc.resize(local_nnz); + + if (rank == 0) { + SendMatrixAParts(size, ptr_a, input); + if (local_nnz > 0) { + const auto &v_a = std::get<2>(input); + const auto &c_a = std::get<4>(input); + std::copy(v_a.begin() + static_cast(ptr_a[start_row]), + v_a.begin() + static_cast(ptr_a[end_row]), val_a_loc.begin()); + std::copy(c_a.begin() + static_cast(ptr_a[start_row]), + c_a.begin() + static_cast(ptr_a[end_row]), col_a_loc.begin()); + } + } else if (local_nnz > 0) { + MPI_Recv(val_a_loc.data(), static_cast(local_nnz), MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + MPI_Recv(static_cast(col_a_loc.data()), static_cast(local_nnz), MPI_UNSIGNED_LONG_LONG, 0, 1, + MPI_COMM_WORLD, MPI_STATUS_IGNORE); + } +} + +void ComputeLocalResult(const std::vector &val_a_loc, const std::vector &col_a_loc, + const std::vector &val_b, const std::vector &col_b, + const std::vector &ptr_b, std::vector &res_loc, int local_rows, size_t cols, + const std::vector &ptr_a, int start_row) { + if (local_rows <= 0 || cols == 0) { + return; + } + + for (int i = 0; i < local_rows; ++i) { + size_t row_idx = static_cast(start_row) + static_cast(i); + size_t row_start = ptr_a[row_idx] - ptr_a[start_row]; + size_t row_end = ptr_a[row_idx + 1] - ptr_a[start_row]; + + for (size_t k = row_start; k < row_end; ++k) { + double a_val = val_a_loc[k]; + size_t a_col = col_a_loc[k]; + if (a_col >= ptr_b.size() - 1) { + continue; + } + + for (size_t j = ptr_b[a_col]; j < ptr_b[a_col + 1]; ++j) { + size_t col_b_idx = col_b[j]; + size_t index = (static_cast(i) * cols) + col_b_idx; + if (index < res_loc.size()) { + res_loc[index] += a_val * val_b[j]; + } + } + } + } +} + +void GatherResults(std::vector &full_res, const std::vector &res_loc, int local_rows, size_t rows, + size_t cols, int rank, int size, OutType &output) { + std::vector counts(size); + std::vector displs(size); + int send_cnt = std::max(local_rows * static_cast(cols), 0); + + MPI_Gather(&send_cnt, 1, MPI_INT, counts.data(), 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (rank == 0) { + if (std::cmp_greater(rows * cols, 10000000)) { + throw std::runtime_error("Matrix too large for MPI broadcast"); + } + full_res.resize(rows * cols, 0.0); + displs[0] = 0; + for (int i = 1; i < size; ++i) { + displs[i] = displs[i - 1] + counts[i - 1]; + } + } + + output.resize(rows * cols); + MPI_Gatherv(res_loc.data(), send_cnt, MPI_DOUBLE, rank == 0 ? output.data() : nullptr, counts.data(), displs.data(), + MPI_DOUBLE, 0, MPI_COMM_WORLD); + MPI_Bcast(output.data(), static_cast(rows * cols), MPI_DOUBLE, 0, MPI_COMM_WORLD); +} + +} // namespace + +FatehovKMatrixCRSMPI::FatehovKMatrixCRSMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool FatehovKMatrixCRSMPI::ValidationImpl() { + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + int is_valid = 0; + if (rank == 0) { + auto &data = GetInput(); + size_t rows = std::get<0>(data); + size_t cols = std::get<1>(data); + if (rows > 0 && cols > 0 && rows <= kMaxRows && cols <= kMaxCols) { + is_valid = 1; + } + } + MPI_Bcast(&is_valid, 1, MPI_INT, 0, MPI_COMM_WORLD); + return is_valid == 1; +} + +bool FatehovKMatrixCRSMPI::PreProcessingImpl() { + return true; +} + +bool FatehovKMatrixCRSMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const auto &input = GetInput(); + size_t rows = 0; + size_t cols = 0; + BroadcastMatrixSizes(rows, cols, rank, input); + + if (rows == 0 || cols == 0 || rows > 10000 || cols > 10000) { + return false; + } + + std::vector val_b{}; + std::vector col_b{}; + std::vector ptr_b{}; + size_t nnz_b = 0; + BroadcastMatrixB(val_b, col_b, ptr_b, nnz_b, rows, rank, input); + + std::vector ptr_a{}; + BroadcastMatrixAStructure(ptr_a, rows, rank, input); + + int local_rows = 0; + int start_row = 0; + int end_row = 0; + DistributeLocalWork(local_rows, start_row, end_row, rows, size, rank); + + if (start_row < 0 || std::cmp_greater(end_row, rows) || start_row >= end_row) { + local_rows = 0; + start_row = 0; + end_row = 0; + } + + std::vector val_a_loc{}; + std::vector col_a_loc{}; + ScatterMatrixA(val_a_loc, col_a_loc, ptr_a, start_row, end_row, rank, size, input); + + std::vector res_loc(static_cast(local_rows) * cols, 0.0); + ComputeLocalResult(val_a_loc, col_a_loc, val_b, col_b, ptr_b, res_loc, local_rows, cols, ptr_a, start_row); + + std::vector full_res{}; + auto &output = GetOutput(); + GatherResults(full_res, res_loc, local_rows, rows, cols, rank, size, output); + + return true; +} + +bool FatehovKMatrixCRSMPI::PostProcessingImpl() { + return true; +} + +} // namespace fatehov_k_matrix_crs diff --git a/tasks/fatehov_k_matrix_crs/report.md b/tasks/fatehov_k_matrix_crs/report.md new file mode 100644 index 0000000000..2602a98e65 --- /dev/null +++ b/tasks/fatehov_k_matrix_crs/report.md @@ -0,0 +1,267 @@ +# Умножение разреженных матриц в формате CRS + +- Студент: Фатехов Камиль Гаярович, группа 3823Б1ФИ3 +- Технология: SEQ | MPI +- Вариант: 4 + +## 1. Введение +**Цель лабораторной**: +Разработка последовательной и параллельной версий умножения разреженных матриц в формате CRS (Compressed Row Storage) и последующее сравнение этих реализаций по производительности. + +**Задачи лабораторной**: +- Реализовать последовательную версию умножения разреженных матриц в формате CRS. + +- Изучить механизмы распределения данных в MPI и реализовать параллельную версию алгоритма. + +- Сравнить производительность и эффективность SEQ и MPI реализаций на различных размерах матриц. +## 2. Постановка задачи +**Задача**: Выполнить умножение двух разреженных матриц `A` `(M×K)` и `B` `(K×N)` с получением матрицы `C` `(M×N)`. + +**Входные данные** + Данные представлены в виде кортежа: + ```cpp + tuple(rows_A, cols_B, + values_A, values_B, + col_indices_A, col_indices_B, + row_ptr_A, row_ptr_B) +``` +где: + +- `rows_A` - количество строк матрицы A (size_t) + +- `cols_B` - количество столбцов матрицы B (size_t) + +- `values_A`, `values_B` - ненулевые значения матриц (vector) + +- `col_indices_A`, `col_indices_B` - индексы столбцов ненулевых элементов `(vector)` + +- ` row_ptr_A`, `row_ptr_B` - указатели на начало строк в массивах значений `(vector)` + +**Выходные данные:** +Плотная матрица `C` размером `M×N`, представленная в виде вектора `(vector)`, хранящего элементы по строкам. + +**Ограничения:** + +- `rows_A` <= `10000` + +- `cols_B` <= `10000` + +Общее количество ненулевых элементов в каждой матрице ≤ `10000000` + + +## 3. Базовый алгоритм (Последовательный) + + **Реализован следующий код**: +```cpp + bool FatehovKMatrixCRSSEQ::RunImpl() { + auto &data = GetInput(); + size_t rows = std::get<0>(data); + size_t cols = std::get<1>(data); + auto &values = std::get<2>(data); + auto &values2 = std::get<3>(data); + auto &col_indices = std::get<4>(data); + auto &col_indices2 = std::get<5>(data); + auto &row_ptr = std::get<6>(data); + auto &row_ptr2 = std::get<7>(data); + + std::vector result(rows * cols, 0.0); + + for (size_t i = 0; i < rows; ++i) { + for (size_t k = row_ptr[i]; k < row_ptr[i + 1]; ++k) { + size_t col_a = col_indices[k]; + double val_a = values[k]; + + for (size_t j = row_ptr2[col_a]; j < row_ptr2[col_a + 1]; ++j) { + size_t col_b = col_indices2[j]; + double val_b = values2[j]; + + size_t index = (i * cols) + col_b; + result[index] += val_a * val_b; + } + } + } + + GetOutput() = result; + return true; +} +``` + +**Алгоритм работы**: +1. Инициализируем результирующую матрицу C нулями размером M×N. + +2. Для каждой строки i матрицы A: + +- Для каждого ненулевого элемента в строке i матрицы A (значение val_a в столбце col_a): + +- Для каждого ненулевого элемента в строке col_a матрицы B (значение val_b в столбце col_b): + +- Увеличиваем элемент C[i][col_b] на val_a × val_b + +3. Возвращаем результирующую матрицу C. + +## 4. Схема распараллеливания + +**Описание**: +- MPI-версия распределяет строки матрицы A между процессами. Каждый процесс умножает свои строки на всю матрицу B, после чего результаты собираются на процессе 0 и рассылаются всем процессам. + +**Алгоритм работы**: + +1. **Инициализация MPI и получение размеров матриц** + +```cpp +MPI_Comm_rank(MPI_COMM_WORLD, &rank); +MPI_Comm_size(MPI_COMM_WORLD, &size); +BroadcastMatrixSizes(rows, cols, rank, input); +``` + +2. **Рассылка структуры матрицы B всем процессам** +```cpp +BroadcastMatrixB(val_b, col_b, ptr_b, nnz_b, rows, rank, input); +``` + +3. **Распределение строк матрицы A по процессам** + +```cpp +DistributeLocalWork(local_rows, start_row, end_row, rows, size, rank); +``` + +4. **Рассылка локальных частей матрицы A каждому процессу** + +```cpp +ScatterMatrixA(val_a_loc, col_a_loc, ptr_a, start_row, end_row, rank, size, input); +``` +5. **Локальное умножение** +```cpp +ComputeLocalResult(val_a_loc, col_a_loc, val_b, col_b, ptr_b, res_loc, local_rows, cols, ptr_a, start_row); +``` + +6. **Сбор и рассылка результатов** +```cpp +GatherResults(full_res, res_loc, local_rows, rows, cols, rank, size, output); +``` + +## 5. Детали реализации + +### **Формат CRS (Compressed Row Storage)** + +Каждая матрица хранится в трёх массивах: + +- `values` - ненулевые значения + +- `col_indices` - индексы столбцов для каждого значения + +- `row_ptr` - указатели на начало каждой строки в массивах значений и индексов + +**Генерация тестовых данных:** +- Для создания разреженных матриц используется линейный конгруэнтный генератор с заданной разреженностью: +```cpp + uint64_t state = 42; + const uint64_t a = 1664525ULL; + const uint64_t c = 1013904223ULL; + const uint64_t m = (1ULL << 22); + + for (size_t i = 0; i < rows; ++i) { + size_t nnz_in_row = 0; + for (size_t j = 0; j < cols; ++j) { + state = (a * state + c) % m; + if (static_cast(state % 1000) / 1000.0 < sparsity) { + double value = ((static_cast(state) / m) * 20.0) - 10.0; + values.push_back(value); + col_indices.push_back(j); + nnz_in_row++; + } + } + row_ptr[i + 1] = row_ptr[i] + nnz_in_row; + } +``` +**Параметры генерации для тестов производительности:** + +- Размер матриц: `1000×1000` + +- Разреженность: `2%` + +- Примерное количество ненулевых элементов: `1000×1000×0.02` = `1000000` на матрицу + +## 6. Экспериментальная среда +- Hardware/OS: AMD RYZEN 5 5600 6-Core Processor, 12-Threads, 16GB, Ubuntu (DevContainer/WSL 2.0) +- Toolchain: GCC 14.1.0, cmake version 3.31.1, Release +- Data: происходит генерация данных через функцию, описанную в пункте 5 + +## 7. Результаты и обсуждение + +### 7.1 Производительность + + +| **Режим** | **Количество процессов** | **Время, с** | **Ускорение** | **Эффективность** | +|-------------|-------|---------|---------|------------| +| seq | 1 | 0.5484 | 1.00 | N/A | +| mpi | 2 | 0.4212 | 1.31 | 65.5% | +| mpi | 4 | 0.4970 | 1.10 | 27.5% | +| mpi | 6 | 0.5160 | 1.06 | 17.6% | + +**Анализ результатов:** + +**Неэффективная параллелизация:** + +- MPI версия показывает очень низкое ускорение + +- Эффективность падает с увеличением числа процессов + +- На 6 процессах ускорение всего 1.06× (почти нет преимущества над SEQ) + +- Причины низкой производительности MPI: + + **Неоптимальное соотношение вычислений/коммуникаций:** + +- Время вычислений: `O(nnz_A × avg_nnz_per_row_B)` для локальных данных + +- Время коммуникаций: рассылка всей матрицы `B` + сбор полной матрицы `C` + + +**Высокие коммуникационные затраты:** + +```cpp +// Рассылка всей матрицы B всем процессам +MPI_Bcast(val_b.data(), nnz_b, MPI_DOUBLE, 0, MPI_COMM_WORLD); +// Сбор и рассылка полной матрицы C +MPI_Gatherv(...); // 25 млн элементов +MPI_Bcast(output.data(), rows * cols, MPI_DOUBLE, 0, MPI_COMM_WORLD); +``` +**Пересылка избыточных данных:** + +- Каждый процесс получает всю матрицу B + +- Каждый процесс в итоге получает всю матрицу C + +**Ограниченный параллелизм алгоритма:** + +- Умножение разреженных матриц имеет сложную структуру зависимостей + +- Простое распределение строк не учитывает реальную вычислительную нагрузку + +- Некоторые строки могут быть почти пустыми, другие - плотными + +**Сравнение с SEQ версией:** + +- SEQ: 0.5484 с (один поток) + +- MPI (2 процесса): 0.4212 с (ускорение всего 1.31×) + +- MPI (6 процессов): 0.5160 с (медленнее чем на 4 процессах) + +**Проблемы масштабируемости:** + +- Рост числа процессов увеличивает коммуникационные накладные расходы + +- Ускорение достигает пика на 2 процессах, затем снижается + +- При 6 процессах MPI версия почти не дает преимущества над SEQ + +## 8. Заключение +В ходе лабораторной работы были успешно реализованы последовательная и параллельная версии алгоритма умножения разреженных матриц в формате CRS. Однако результаты тестирования производительности выявили серьезные проблемы эффективности MPI-реализации. + +## 9. Источники +[Линейный конгруэнтный генератор](https://www.tutorialspoint.com/cplusplus-program-to-implement-the-linear-congruential-generator-for-pseudo-random-number-generation) + + + diff --git a/tasks/fatehov_k_matrix_crs/seq/include/ops_seq.hpp b/tasks/fatehov_k_matrix_crs/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..706e1817e0 --- /dev/null +++ b/tasks/fatehov_k_matrix_crs/seq/include/ops_seq.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "fatehov_k_matrix_crs/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace fatehov_k_matrix_crs { + +class FatehovKMatrixCRSSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit FatehovKMatrixCRSSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; + static const int kMaxRows = 10000; + static const int kMaxCols = 10000; + static const int kMaxNonZero = 10000000; +}; + +} // namespace fatehov_k_matrix_crs diff --git a/tasks/fatehov_k_matrix_crs/seq/src/ops_seq.cpp b/tasks/fatehov_k_matrix_crs/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..150d69c2bc --- /dev/null +++ b/tasks/fatehov_k_matrix_crs/seq/src/ops_seq.cpp @@ -0,0 +1,73 @@ +#include "fatehov_k_matrix_crs/seq/include/ops_seq.hpp" + +#include +#include +#include + +#include "fatehov_k_matrix_crs/common/include/common.hpp" + +namespace fatehov_k_matrix_crs { + +FatehovKMatrixCRSSEQ::FatehovKMatrixCRSSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = std::vector(); +} + +bool FatehovKMatrixCRSSEQ::ValidationImpl() { + auto &data = GetInput(); + size_t rows = std::get<0>(data); + size_t cols = std::get<1>(data); + auto &values = std::get<2>(data); + auto &values2 = std::get<3>(data); + auto &col_indices = std::get<4>(data); + auto &col_indices2 = std::get<5>(data); + auto &row_ptr = std::get<6>(data); + auto &row_ptr2 = std::get<7>(data); + + return (rows > 0 && rows <= kMaxRows) && (cols > 0 && cols <= kMaxCols) && (values.size() == col_indices.size()) && + (values2.size() == col_indices2.size()) && (values.size() <= kMaxNonZero) && (values2.size() <= kMaxNonZero) && + (row_ptr.size() == rows + 1) && (row_ptr2.size() == rows + 1) && (!values.empty()) && (!values2.empty()); +} + +bool FatehovKMatrixCRSSEQ::PreProcessingImpl() { + return true; +} + +bool FatehovKMatrixCRSSEQ::RunImpl() { + auto &data = GetInput(); + size_t rows = std::get<0>(data); + size_t cols = std::get<1>(data); + auto &values = std::get<2>(data); + auto &values2 = std::get<3>(data); + auto &col_indices = std::get<4>(data); + auto &col_indices2 = std::get<5>(data); + auto &row_ptr = std::get<6>(data); + auto &row_ptr2 = std::get<7>(data); + + std::vector result(rows * cols, 0.0); + + for (size_t i = 0; i < rows; ++i) { + for (size_t k = row_ptr[i]; k < row_ptr[i + 1]; ++k) { + size_t col_a = col_indices[k]; + double val_a = values[k]; + + for (size_t j = row_ptr2[col_a]; j < row_ptr2[col_a + 1]; ++j) { + size_t col_b = col_indices2[j]; + double val_b = values2[j]; + + size_t index = (i * cols) + col_b; + result[index] += val_a * val_b; + } + } + } + + GetOutput() = result; + return true; +} + +bool FatehovKMatrixCRSSEQ::PostProcessingImpl() { + return true; +} + +} // namespace fatehov_k_matrix_crs diff --git a/tasks/fatehov_k_matrix_crs/settings.json b/tasks/fatehov_k_matrix_crs/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/fatehov_k_matrix_crs/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/fatehov_k_matrix_crs/tests/.clang-tidy b/tasks/fatehov_k_matrix_crs/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/fatehov_k_matrix_crs/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/fatehov_k_matrix_crs/tests/functional/main.cpp b/tasks/fatehov_k_matrix_crs/tests/functional/main.cpp new file mode 100644 index 0000000000..e86c34cf57 --- /dev/null +++ b/tasks/fatehov_k_matrix_crs/tests/functional/main.cpp @@ -0,0 +1,100 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "fatehov_k_matrix_crs/common/include/common.hpp" +#include "fatehov_k_matrix_crs/mpi/include/ops_mpi.hpp" +#include "fatehov_k_matrix_crs/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace fatehov_k_matrix_crs { + +class FatehovKRunFuncTestsMatrixCRS : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType &test_param) { + std::string out = std::to_string(std::get<0>(test_param)) + "_matrix_" + std::to_string(std::get<1>(test_param)) + + "x" + std::to_string(std::get<2>(test_param)); + std::ranges::replace(out, '-', 'm'); + std::ranges::replace(out, '.', '_'); + return out; + } + + protected: + void SetUp() override { + TestType params = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + size_t rows = std::get<1>(params); + size_t cols = std::get<2>(params); + std::vector values = std::get<3>(params); + std::vector values2 = std::get<4>(params); + std::vector col_indices = std::get<5>(params); + std::vector col_indices2 = std::get<6>(params); + std::vector row_ptr = std::get<7>(params); + std::vector row_ptr2 = std::get<8>(params); + std::vector expected = std::get<9>(params); + + input_data_ = std::make_tuple(rows, cols, values, values2, col_indices, col_indices2, row_ptr, row_ptr2); + expected_result_ = expected; + } + + bool CheckTestOutputData(OutType &output_data) final { + if (output_data.size() != expected_result_.size()) { + return false; + } + + for (size_t i = 0; i < output_data.size(); ++i) { + if (std::fabs(expected_result_[i] - output_data[i]) > 1e-10) { + return false; + } + } + return true; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_ = std::make_tuple(0, 0, std::vector{}, std::vector{}, std::vector{}, + std::vector{}, std::vector{}, std::vector{}); + OutType expected_result_; +}; + +namespace { + +TEST_P(FatehovKRunFuncTestsMatrixCRS, MatrixMultiplicationCRS) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = { + std::make_tuple(1, 2, 2, std::vector{1.0, 0.0, 0.0, 1.0}, std::vector{2.0, 3.0, 4.0, 5.0}, + std::vector{0, 1, 0, 1}, std::vector{0, 1, 0, 1}, std::vector{0, 2, 4}, + std::vector{0, 2, 4}, std::vector{2.0, 3.0, 4.0, 5.0}), + + std::make_tuple(2, 2, 2, std::vector{2.0, 0.0, 0.0, 2.0}, std::vector{1.0, 2.0, 3.0, 4.0}, + std::vector{0, 1, 0, 1}, std::vector{0, 1, 0, 1}, std::vector{0, 2, 4}, + std::vector{0, 2, 4}, std::vector{2.0, 4.0, 6.0, 8.0}), + + std::make_tuple(3, 2, 2, std::vector{1.0, 0.0, 0.0, 2.0}, std::vector{1.0, 2.0, 3.0, 4.0}, + std::vector{0, 1, 0, 1}, std::vector{0, 1, 0, 1}, std::vector{0, 2, 4}, + std::vector{0, 2, 4}, std::vector{1.0, 2.0, 6.0, 8.0})}; + +const auto kTestTasksList = + std::tuple_cat(ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_fatehov_k_matrix_crs), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_fatehov_k_matrix_crs)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kPerfTestName = FatehovKRunFuncTestsMatrixCRS::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(TestMatrixCRS, FatehovKRunFuncTestsMatrixCRS, kGtestValues, kPerfTestName); + +} // namespace + +} // namespace fatehov_k_matrix_crs diff --git a/tasks/fatehov_k_matrix_crs/tests/performance/main.cpp b/tasks/fatehov_k_matrix_crs/tests/performance/main.cpp new file mode 100644 index 0000000000..972159ec51 --- /dev/null +++ b/tasks/fatehov_k_matrix_crs/tests/performance/main.cpp @@ -0,0 +1,118 @@ +#include + +#include +#include +#include +#include +#include + +#include "fatehov_k_matrix_crs/common/include/common.hpp" +#include "fatehov_k_matrix_crs/mpi/include/ops_mpi.hpp" +#include "fatehov_k_matrix_crs/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace fatehov_k_matrix_crs { + +class FatehovKRunPerfTestsMatrixCRS : public ppc::util::BaseRunPerfTests { + InType input_data_ = std::make_tuple(0, 0, std::vector{}, std::vector{}, std::vector{}, + std::vector{}, std::vector{}, std::vector{}); + OutType expected_result_; + + void SetUp() override { + const size_t rows = 1000; + const size_t cols = 1000; + const double sparsity = 0.02; + + std::vector values{}; + std::vector values2{}; + std::vector col_indices{}; + std::vector col_indices2{}; + std::vector row_ptr(rows + 1, 0); + std::vector row_ptr2(rows + 1, 0); + + uint64_t state = 42; + const uint64_t a = 1664525ULL; + const uint64_t c = 1013904223ULL; + const uint64_t m = (1ULL << 22); + + for (size_t i = 0; i < rows; ++i) { + size_t nnz_in_row = 0; + for (size_t j = 0; j < cols; ++j) { + state = (a * state + c) % m; + if (static_cast(state % 1000) / 1000.0 < sparsity) { + double value = ((static_cast(state) / m) * 20.0) - 10.0; + values.push_back(value); + col_indices.push_back(j); + nnz_in_row++; + } + } + row_ptr[i + 1] = row_ptr[i] + nnz_in_row; + } + + state = 123; + for (size_t i = 0; i < rows; ++i) { + size_t nnz_in_row = 0; + for (size_t j = 0; j < cols; ++j) { + state = (a * state + c) % m; + if (static_cast(state % 1000) / 1000.0 < sparsity) { + double value = ((static_cast(state) / m) * 20.0) - 10.0; + values2.push_back(value); + col_indices2.push_back(j); + nnz_in_row++; + } + } + row_ptr2[i + 1] = row_ptr2[i] + nnz_in_row; + } + + std::vector computed_result(rows * cols, 0.0); + + for (size_t i = 0; i < rows; ++i) { + for (size_t k = row_ptr[i]; k < row_ptr[i + 1]; ++k) { + size_t col_a = col_indices[k]; + double val_a = values[k]; + + for (size_t j = row_ptr2[col_a]; j < row_ptr2[col_a + 1]; ++j) { + size_t col_b = col_indices2[j]; + double val_b = values2[j]; + + size_t index = (i * cols) + col_b; + computed_result[index] += val_a * val_b; + } + } + } + + expected_result_ = computed_result; + input_data_ = std::make_tuple(rows, cols, values, values2, col_indices, col_indices2, row_ptr, row_ptr2); + } + + bool CheckTestOutputData(OutType &output_data) final { + if (output_data.size() != expected_result_.size()) { + return false; + } + for (size_t i = 0; i < output_data.size(); ++i) { + if (std::fabs(expected_result_[i] - output_data[i]) > 1e-10) { + return false; + } + } + return true; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(FatehovKRunPerfTestsMatrixCRS, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks(PPC_SETTINGS_fatehov_k_matrix_crs); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = FatehovKRunPerfTestsMatrixCRS::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunPerfTest, FatehovKRunPerfTestsMatrixCRS, kGtestValues, kPerfTestName); + +} // namespace fatehov_k_matrix_crs