diff --git a/tasks/trofimov_n_mult_matrix_cannon/common/include/common.hpp b/tasks/trofimov_n_mult_matrix_cannon/common/include/common.hpp new file mode 100644 index 0000000000..8549c6a468 --- /dev/null +++ b/tasks/trofimov_n_mult_matrix_cannon/common/include/common.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +#include "task/include/task.hpp" + +namespace trofimov_n_mult_matrix_cannon { + +using Matrix = std::vector; + +using InType = std::tuple; +using OutType = Matrix; + +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +} // namespace trofimov_n_mult_matrix_cannon diff --git a/tasks/trofimov_n_mult_matrix_cannon/info.json b/tasks/trofimov_n_mult_matrix_cannon/info.json new file mode 100644 index 0000000000..a603679ded --- /dev/null +++ b/tasks/trofimov_n_mult_matrix_cannon/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Никита", + "last_name": "Трофимов", + "middle_name": "Сергеевич", + "group_number": "3823Б1ПР4", + "task_number": "1" + } +} diff --git a/tasks/trofimov_n_mult_matrix_cannon/mpi/include/ops_mpi.hpp b/tasks/trofimov_n_mult_matrix_cannon/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..60be82917d --- /dev/null +++ b/tasks/trofimov_n_mult_matrix_cannon/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "task/include/task.hpp" +#include "trofimov_n_mult_matrix_cannon/common/include/common.hpp" + +namespace trofimov_n_mult_matrix_cannon { + +class TrofimovNMultMatrixCanonMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit TrofimovNMultMatrixCanonMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace trofimov_n_mult_matrix_cannon diff --git a/tasks/trofimov_n_mult_matrix_cannon/mpi/src/ops_mpi.cpp b/tasks/trofimov_n_mult_matrix_cannon/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..8924a103b9 --- /dev/null +++ b/tasks/trofimov_n_mult_matrix_cannon/mpi/src/ops_mpi.cpp @@ -0,0 +1,214 @@ +#include "trofimov_n_mult_matrix_cannon/mpi/include/ops_mpi.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "trofimov_n_mult_matrix_cannon/common/include/common.hpp" + +namespace trofimov_n_mult_matrix_cannon { + +namespace { + +void MultiplySequential(const std::vector &a, const std::vector &b, int n, + std::vector &result) { + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + for (int k = 0; k < n; k++) { + result[(i * n) + j] += a[(i * n) + k] * b[(k * n) + j]; + } + } + } +} + +MPI_Comm CreateCartesianComm(int q) { + const std::array dims = {q, q}; + const std::array periods = {1, 1}; + + MPI_Comm cart = MPI_COMM_NULL; + MPI_Cart_create(MPI_COMM_WORLD, 2, dims.data(), periods.data(), 1, &cart); + return cart; +} + +void ScatterBlocks(MPI_Comm cart, int block, int world_size, const std::vector &matrix_a, + const std::vector &matrix_b, int matrix_size, std::vector &block_a, + std::vector &block_b) { + const std::size_t block_size = static_cast(block) * static_cast(block); + + int rank = 0; + MPI_Comm_rank(cart, &rank); + + if (rank == 0) { + for (int process = 0; process < world_size; process++) { + std::array coords{}; + MPI_Cart_coords(cart, process, 2, coords.data()); + + std::vector local_a(block_size); + std::vector local_b(block_size); + + for (int i = 0; i < block; i++) { + for (int j = 0; j < block; j++) { + const int gi = (coords[0] * block) + i; + const int gj = (coords[1] * block) + j; + local_a[(i * block) + j] = matrix_a[(gi * matrix_size) + gj]; + local_b[(i * block) + j] = matrix_b[(gi * matrix_size) + gj]; + } + } + + if (process == 0) { + block_a = std::move(local_a); + block_b = std::move(local_b); + } else { + MPI_Send(local_a.data(), static_cast(block_size), MPI_DOUBLE, process, 0, cart); + MPI_Send(local_b.data(), static_cast(block_size), MPI_DOUBLE, process, 1, cart); + } + } + } else { + MPI_Recv(block_a.data(), static_cast(block_size), MPI_DOUBLE, 0, 0, cart, MPI_STATUS_IGNORE); + MPI_Recv(block_b.data(), static_cast(block_size), MPI_DOUBLE, 0, 1, cart, MPI_STATUS_IGNORE); + } +} + +void InitialShift(MPI_Comm cart, int row, int col, std::vector &block_a, std::vector &block_b, + std::size_t block_size) { + int left = 0; + int right = 0; + int up = 0; + int down = 0; + + for (int i = 0; i < row; i++) { + MPI_Cart_shift(cart, 1, -1, &right, &left); + MPI_Sendrecv_replace(block_a.data(), static_cast(block_size), MPI_DOUBLE, left, 0, right, 0, cart, + MPI_STATUS_IGNORE); + } + + for (int i = 0; i < col; i++) { + MPI_Cart_shift(cart, 0, -1, &down, &up); + MPI_Sendrecv_replace(block_b.data(), static_cast(block_size), MPI_DOUBLE, up, 1, down, 1, cart, + MPI_STATUS_IGNORE); + } +} + +void CannonMultiply(MPI_Comm cart, int q, int block, std::vector &block_a, std::vector &block_b, + std::vector &block_c) { + const std::size_t block_size = static_cast(block) * static_cast(block); + + int left = 0; + int right = 0; + int up = 0; + int down = 0; + + for (int step = 0; step < q; step++) { + for (int i = 0; i < block; i++) { + for (int j = 0; j < block; j++) { + for (int k = 0; k < block; k++) { + block_c[(i * block) + j] += block_a[(i * block) + k] * block_b[(k * block) + j]; + } + } + } + + MPI_Cart_shift(cart, 1, -1, &right, &left); + MPI_Sendrecv_replace(block_a.data(), static_cast(block_size), MPI_DOUBLE, left, 0, right, 0, cart, + MPI_STATUS_IGNORE); + + MPI_Cart_shift(cart, 0, -1, &down, &up); + MPI_Sendrecv_replace(block_b.data(), static_cast(block_size), MPI_DOUBLE, up, 1, down, 1, cart, + MPI_STATUS_IGNORE); + } +} + +void GatherResult(MPI_Comm cart, int world_size, int block, int matrix_size, const std::vector &block_c, + std::vector &result) { + const std::size_t block_size = static_cast(block) * static_cast(block); + + std::vector all_blocks(static_cast(world_size) * block_size); + + MPI_Allgather(block_c.data(), static_cast(block_size), MPI_DOUBLE, all_blocks.data(), + static_cast(block_size), MPI_DOUBLE, cart); + + for (int process = 0; process < world_size; process++) { + std::array coords{}; + MPI_Cart_coords(cart, process, 2, coords.data()); + + const double *src = &all_blocks[static_cast(process) * block_size]; + + for (int i = 0; i < block; i++) { + for (int j = 0; j < block; j++) { + const int gi = (coords[0] * block) + i; + const int gj = (coords[1] * block) + j; + result[(gi * matrix_size) + gj] = src[(i * block) + j]; + } + } + } +} + +} // namespace + +TrofimovNMultMatrixCanonMPI::TrofimovNMultMatrixCanonMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool TrofimovNMultMatrixCanonMPI::ValidationImpl() { + return true; +} + +bool TrofimovNMultMatrixCanonMPI::PreProcessingImpl() { + const auto &[a_vector, b_vector, matrix_size] = GetInput(); + if (matrix_size > 0) { + GetOutput().assign(static_cast(matrix_size) * static_cast(matrix_size), 0.0); + } + return true; +} + +bool TrofimovNMultMatrixCanonMPI::RunImpl() { + const auto &[matrix_a, matrix_b, matrix_size] = GetInput(); + auto &result_matrix = GetOutput(); + + if (matrix_size <= 0) { + return true; + } + + int world_size = 0; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + + const int q = static_cast(std::sqrt(world_size)); + if ((q * q != world_size) || (matrix_size % q != 0)) { + MultiplySequential(matrix_a, matrix_b, matrix_size, result_matrix); + return true; + } + + const int block = matrix_size / q; + MPI_Comm cart = CreateCartesianComm(q); + + int rank = 0; + MPI_Comm_rank(cart, &rank); + + std::array coords{}; + MPI_Cart_coords(cart, rank, 2, coords.data()); + + const std::size_t block_size = static_cast(block) * static_cast(block); + + std::vector block_a(block_size); + std::vector block_b(block_size); + std::vector block_c(block_size, 0.0); + + ScatterBlocks(cart, block, world_size, matrix_a, matrix_b, matrix_size, block_a, block_b); + + InitialShift(cart, coords[0], coords[1], block_a, block_b, block_size); + CannonMultiply(cart, q, block, block_a, block_b, block_c); + GatherResult(cart, world_size, block, matrix_size, block_c, result_matrix); + + MPI_Comm_free(&cart); + return true; +} + +bool TrofimovNMultMatrixCanonMPI::PostProcessingImpl() { + return true; +} + +} // namespace trofimov_n_mult_matrix_cannon diff --git a/tasks/trofimov_n_mult_matrix_cannon/report.md b/tasks/trofimov_n_mult_matrix_cannon/report.md new file mode 100644 index 0000000000..073fcfd4e7 --- /dev/null +++ b/tasks/trofimov_n_mult_matrix_cannon/report.md @@ -0,0 +1,99 @@ +# Умножение матриц с использованием алгоритма Кэннона + +* Студент: Трофимов Никита, группа 3823Б1ПР4 +* Технология: ALL (SEQ + MPI) +* Вариант: 1 + +## 1. Введение + +В отчёте представлена реализация и анализ производительности умножения плотных матриц с использованием алгоритма Кэннона для параллельного исполнения с MPI, а также последовательная реализация для сравнения. Цель – оценить масштабируемость и эффективность параллельного подхода по сравнению с последовательным. + +## 2. Постановка задачи + +Необходимо перемножить две квадратные плотные матрицы типа `double`. Входные данные: матрицы A и B размером N x N. Выходные данные: матрица C = A * B. Ограничения: N должно делиться на квадратный корень из числа MPI процессов. + +## 3. Базовый алгоритм (последовательный) + +Последовательный алгоритм исaпользует стандартные тройные вложенные циклы по индексам i, j, k: + +``` +for i in 0..N-1: + for j in 0..N-1: + C[i][j] = 0 + for k in 0..N-1: + C[i][j] += A[i][k] * B[k][j] +``` + +## 4. Схема параллелизации + +### MPI (алгоритм Кэннона) + +* **Распределение данных:** Матрицы делятся на блоки размера (N/q) x (N/q), где q = sqrt(число процессов). Каждый процесс получает один блок A и один блок B. +* **Схема коммуникаций:** Двумерная декартова топология с периодическими граничными условиями. Изначально блоки A сдвигаются влево на номер строки, блоки B — вверх на номер столбца. +* **Вычисления:** Каждый процесс перемножает локальные блоки и выполняет q шагов циклических сдвигов A влево и B вверх. +* **Сбор результатов:** Используется `MPI_Allgather` для сбора частичных результатов. + +Пример схемы для сетки процессов 2x2: + +``` +Сетка процессов: +[0,0] [0,1] +[1,0] [1,1] +``` + +## 5. Детали реализации + +* **Структура кода:** + + * `ops_seq.hpp/cpp` — последовательное умножение + * `ops_mpi.hpp/cpp` — MPI реализация алгоритма Кэннона +* **Предположения:** N делится на sqrt(world_size) +* **Использование памяти:** Блоки выделяются на каждый процесс; общий объём памяти на процесс = 3 * block_size (A, B, C) +* **Важные моменты кода:** + + * Функция `ScatterBlocks` распределяет блоки между процессами. + * `InitialShift` выполняет начальные сдвиги блоков перед умножением. + * `CannonMultiply` содержит основной цикл умножения блоков с циклической перестановкой A и B. + * `GatherResult` собирает все блоки в результирующую матрицу. + * В последовательной версии `MultiplySequential` используется тройной вложенный цикл. + +## 6. Экспериментальная установка + +* **Аппаратное/ПО:** CPU AMD Ryzen 7700, RAM > 8GB, Windows OS +* **Инструменты:** g++/MSVC, сборка Release +* **Окружение:** PPC_NUM_PROC задаётся через `mpiexec -n X` +* **Данные:** Генерируются последовательно, значения матриц возрастают/убывают для тестирования + +## 7. Результаты и обсуждение + +### 7.1 Корректность + +Корректность проверялась сравнением с последовательной реализацией для маленьких матриц (1x1, 2x2, 4x4). Все MPI-прогоны дали правильный результат. + +### 7.2 Производительность + +Время выполнения для матрицы N=128: + +| Режим | Кол-во процессов | Время, с | Ускорение | Эффективность | +| ----- | ---------------- | -------- | --------- | ------------- | +| SEQ | 1 | 0.00275 | 1.00 | N/A | +| MPI | 1 | 0.00398 | 0.69 | 69% | +| MPI | 2 | 0.00344 | 0.80 | 40% | +| MPI | 4 | 0.00122 | 2.25 | 56% | +| MPI | 8 | 0.00342 | 0.80 | 10% | + +**Обсуждение:** MPI-реализация хорошо масштабируется до 4 процессов для маленькой матрицы. При увеличении числа процессов свыше 4, накладные расходы на коммуникацию начинают доминировать. Последовательная реализация быстрее на одном потоке для маленьких матриц, но для больших N будет крайне медленной. + +## 8. Выводы + +* Алгоритм Кэннона эффективно параллелизует умножение матриц. +* MPI демонстрирует ускорение при умеренном количестве процессов. +* Накладные расходы на коммуникацию ограничивают масштабируемость для маленьких матриц. +* Последовательная реализация практична только для небольших матриц. +* Важные моменты кода, такие как распределение блоков, начальные сдвиги и сбор результата, критичны для корректной работы алгоритма. + +## 9. Ссылки + +1. H. T. Kung, C. E. Leiserson. Algorithms for Matrix Multiplication on a Mesh-connected Multiprocessor. 1978. +2. Документация MPI. [https://www.mpi-forum.org/docs/](https://www.mpi-forum.org/docs/) +3. Сысоев А. В. Лекции по параллельному программированию. diff --git a/tasks/trofimov_n_mult_matrix_cannon/seq/include/ops_seq.hpp b/tasks/trofimov_n_mult_matrix_cannon/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..6cd80d1731 --- /dev/null +++ b/tasks/trofimov_n_mult_matrix_cannon/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "task/include/task.hpp" +#include "trofimov_n_mult_matrix_cannon/common/include/common.hpp" + +namespace trofimov_n_mult_matrix_cannon { + +class TrofimovNMultMatrixCanonSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit TrofimovNMultMatrixCanonSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace trofimov_n_mult_matrix_cannon diff --git a/tasks/trofimov_n_mult_matrix_cannon/seq/src/ops_seq.cpp b/tasks/trofimov_n_mult_matrix_cannon/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..60abdf26a5 --- /dev/null +++ b/tasks/trofimov_n_mult_matrix_cannon/seq/src/ops_seq.cpp @@ -0,0 +1,48 @@ +#include "trofimov_n_mult_matrix_cannon/seq/include/ops_seq.hpp" + +#include + +#include "trofimov_n_mult_matrix_cannon/common/include/common.hpp" + +namespace trofimov_n_mult_matrix_cannon { + +TrofimovNMultMatrixCanonSEQ::TrofimovNMultMatrixCanonSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; +} + +bool TrofimovNMultMatrixCanonSEQ::ValidationImpl() { + return true; +} + +bool TrofimovNMultMatrixCanonSEQ::PreProcessingImpl() { + const auto &[a_vector, b_vector, matrix_size] = GetInput(); + if (matrix_size > 0) { + GetOutput().assign(static_cast(matrix_size) * static_cast(matrix_size), 0.0); + } + return true; +} + +bool TrofimovNMultMatrixCanonSEQ::RunImpl() { + const auto &[matrix_a, matrix_b, matrix_size] = GetInput(); + auto &result_matrix = GetOutput(); + + if (matrix_size <= 0) { + return true; + } + + for (int i = 0; i < matrix_size; i++) { + for (int j = 0; j < matrix_size; j++) { + for (int k = 0; k < matrix_size; k++) { + result_matrix[(i * matrix_size) + j] += matrix_a[(i * matrix_size) + k] * matrix_b[(k * matrix_size) + j]; + } + } + } + return true; +} + +bool TrofimovNMultMatrixCanonSEQ::PostProcessingImpl() { + return true; +} + +} // namespace trofimov_n_mult_matrix_cannon diff --git a/tasks/trofimov_n_mult_matrix_cannon/settings.json b/tasks/trofimov_n_mult_matrix_cannon/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/trofimov_n_mult_matrix_cannon/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/trofimov_n_mult_matrix_cannon/tests/.clang-tidy b/tasks/trofimov_n_mult_matrix_cannon/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/trofimov_n_mult_matrix_cannon/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/trofimov_n_mult_matrix_cannon/tests/functional/main.cpp b/tasks/trofimov_n_mult_matrix_cannon/tests/functional/main.cpp new file mode 100644 index 0000000000..c74c581821 --- /dev/null +++ b/tasks/trofimov_n_mult_matrix_cannon/tests/functional/main.cpp @@ -0,0 +1,129 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "trofimov_n_mult_matrix_cannon/common/include/common.hpp" +#include "trofimov_n_mult_matrix_cannon/mpi/include/ops_mpi.hpp" +#include "trofimov_n_mult_matrix_cannon/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace trofimov_n_mult_matrix_cannon { + +namespace { + +template +void FillIota(Container &container, T start) { + for (auto &value : container) { + value = start; + ++start; + } +} + +} // namespace + +class TrofimovNFuncTestsMultMatrixCanon : public ppc::util::BaseRunFuncTests { + public: + static std::string PrintTestParam(const TestType ¶m) { + return std::get<1>(param); + } + + protected: + void SetUp() override { + const auto ¶m = std::get(ppc::util::GTestParamIndex::kTestParams)>(GetParam()); + const std::string &name = std::get<1>(param); + + if (name == "n1") { + n_ = 1; + A_ = {2.0}; + B_ = {3.0}; + } else if (name == "n2_identity") { + n_ = 2; + A_ = {1.0, 0.0, 0.0, 1.0}; + B_ = {5.0, 6.0, 7.0, 8.0}; + } else if (name == "n2_zero") { + n_ = 2; + A_ = {0.0, 0.0, 0.0, 0.0}; + B_ = {1.0, 2.0, 3.0, 4.0}; + } else if (name == "n4") { + n_ = 4; + A_.resize(static_cast(n_) * n_); + B_.resize(static_cast(n_) * n_); + FillIota(A_, 1.0); + FillIota(B_, -1.0); + } else if (name == "invalid_n") { + n_ = 0; + } + + input_data_ = std::make_tuple(A_, B_, n_); + } + + InType GetTestInputData() final { + return input_data_; + } + + bool CheckTestOutputData(OutType &output) final { + const auto &[A, B, n] = input_data_; + + if (n <= 0 || A.size() != static_cast(n) * static_cast(n) || + B.size() != static_cast(n) * static_cast(n)) { + return true; + } + + std::vector expected(static_cast(n) * n, 0.0); + + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + double sum = 0.0; + for (int k = 0; k < n; ++k) { + sum += A[(i * n) + k] * B[(k * n) + j]; + } + expected[(i * n) + j] = sum; + } + } + + const double eps = 1e-9; + for (std::size_t i = 0; i < expected.size(); ++i) { + if (std::fabs(expected[i] - output[i]) > eps) { + return false; + } + } + return true; + } + + private: + Matrix A_; + Matrix B_; + int n_{0}; + InType input_data_; +}; + +TEST_P(TrofimovNFuncTestsMultMatrixCanon, RunFuncTests) { + ExecuteTest(GetParam()); +} + +namespace { + +const std::array kTestParams = {TestType{InType{}, "n1"}, TestType{InType{}, "n2_identity"}, + TestType{InType{}, "n2_zero"}, TestType{InType{}, "n4"}, + TestType{InType{}, "invalid_n"}}; + +const auto kTestTasksList = std::tuple_cat(ppc::util::AddFuncTask( + kTestParams, PPC_SETTINGS_trofimov_n_mult_matrix_cannon), + ppc::util::AddFuncTask( + kTestParams, PPC_SETTINGS_trofimov_n_mult_matrix_cannon)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kTestName = TrofimovNFuncTestsMultMatrixCanon::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(MatrixCanonFuncTests, TrofimovNFuncTestsMultMatrixCanon, kGtestValues, kTestName); + +} // namespace + +} // namespace trofimov_n_mult_matrix_cannon diff --git a/tasks/trofimov_n_mult_matrix_cannon/tests/performance/main.cpp b/tasks/trofimov_n_mult_matrix_cannon/tests/performance/main.cpp new file mode 100644 index 0000000000..c6d17eb2fc --- /dev/null +++ b/tasks/trofimov_n_mult_matrix_cannon/tests/performance/main.cpp @@ -0,0 +1,66 @@ +#include + +#include +#include + +#include "trofimov_n_mult_matrix_cannon/common/include/common.hpp" +#include "trofimov_n_mult_matrix_cannon/mpi/include/ops_mpi.hpp" +#include "trofimov_n_mult_matrix_cannon/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace trofimov_n_mult_matrix_cannon { + +namespace { + +template +void FillIota(Container &container, T start) { + for (auto &value : container) { + value = start; + ++start; + } +} + +} // namespace + +class TrofimovNPerfTestsMultMatrixCanon : public ppc::util::BaseRunPerfTests { + protected: + void SetUp() override { + constexpr int kN = 128; + + Matrix a_matrix(static_cast(kN) * kN); + Matrix b_matrix(static_cast(kN) * kN); + + FillIota(a_matrix, 1.0); + FillIota(b_matrix, -1.0); + + input_data_ = std::make_tuple(a_matrix, b_matrix, kN); + } + + bool CheckTestOutputData(OutType &output_data) final { + (void)output_data; + return true; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_; +}; + +TEST_P(TrofimovNPerfTestsMultMatrixCanon, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_trofimov_n_mult_matrix_cannon); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = TrofimovNPerfTestsMultMatrixCanon::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, TrofimovNPerfTestsMultMatrixCanon, kGtestValues, kPerfTestName); + +} // namespace trofimov_n_mult_matrix_cannon