diff --git a/tasks/likhanov_m_multi_step_scheme/common/include/common.hpp b/tasks/likhanov_m_multi_step_scheme/common/include/common.hpp new file mode 100644 index 0000000000..c753a63cc5 --- /dev/null +++ b/tasks/likhanov_m_multi_step_scheme/common/include/common.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include "task/include/task.hpp" + +namespace likhanov_m_multi_step_scheme { + +using InType = int; +using OutType = int; +using TestType = std::tuple; +using BaseTask = ppc::task::Task; + +struct Rect { + double x_left; + double x_right; + double y_left; + double y_right; + + double value{0.0}; + double characteristic{0.0}; + + Rect(double x_left_value, + double x_right_value, + double y_left_value, + double y_right_value) + : x_left(x_left_value), + x_right(x_right_value), + y_left(y_left_value), + y_right(y_right_value) {} +}; + +inline double TestFunction(double x, double y) { + return std::sin(x) + std::cos(y); +} + +} // namespace likhanov_m_multi_step_scheme diff --git a/tasks/likhanov_m_multi_step_scheme/data/pic.jpg b/tasks/likhanov_m_multi_step_scheme/data/pic.jpg new file mode 100644 index 0000000000..637624238c Binary files /dev/null and b/tasks/likhanov_m_multi_step_scheme/data/pic.jpg differ diff --git a/tasks/likhanov_m_multi_step_scheme/info.json b/tasks/likhanov_m_multi_step_scheme/info.json new file mode 100644 index 0000000000..3beccb92ae --- /dev/null +++ b/tasks/likhanov_m_multi_step_scheme/info.json @@ -0,0 +1,9 @@ +{ + "student": { + "first_name": "Матвей", + "last_name": "Лиханов", + "middle_name": "Дмитриевич", + "group_number": "3823Б1ПР1", + "task_number": "3" + } +} diff --git a/tasks/likhanov_m_multi_step_scheme/mpi/include/ops_mpi.hpp b/tasks/likhanov_m_multi_step_scheme/mpi/include/ops_mpi.hpp new file mode 100644 index 0000000000..1a94432387 --- /dev/null +++ b/tasks/likhanov_m_multi_step_scheme/mpi/include/ops_mpi.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "likhanov_m_multi_step_scheme/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace likhanov_m_multi_step_scheme { + +class LikhanovMMultiStepSchemeMPI : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kMPI; + } + explicit LikhanovMMultiStepSchemeMPI(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace likhanov_m_multi_step_scheme diff --git a/tasks/likhanov_m_multi_step_scheme/mpi/src/ops_mpi.cpp b/tasks/likhanov_m_multi_step_scheme/mpi/src/ops_mpi.cpp new file mode 100644 index 0000000000..77471ec3a2 --- /dev/null +++ b/tasks/likhanov_m_multi_step_scheme/mpi/src/ops_mpi.cpp @@ -0,0 +1,107 @@ +#include "likhanov_m_multi_step_scheme/mpi/include/ops_mpi.hpp" + +#include + +#include +#include + +#include "likhanov_m_multi_step_scheme/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace likhanov_m_multi_step_scheme { + +LikhanovMMultiStepSchemeMPI::LikhanovMMultiStepSchemeMPI(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool LikhanovMMultiStepSchemeMPI::ValidationImpl() { + return (GetInput() > 0) && (GetOutput() == 0); +} + +bool LikhanovMMultiStepSchemeMPI::PreProcessingImpl() { + GetOutput() = 2 * GetInput(); + return GetOutput() > 0; +} + +bool LikhanovMMultiStepSchemeMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const int steps = GetInput(); + if (steps <= 0) { + return false; + } + + std::vector rects; + rects.emplace_back(0.0, 1.0, 0.0, 1.0); + + const double L = 2.0; + + for (int step = 0; step < steps; step++) { + double local_best = -1e9; + int local_index = -1; + + for (int i = rank; i < static_cast(rects.size()); i += size) { + Rect& r = rects[i]; + + const double cx = 0.5 * (r.x_left + r.x_right); + const double cy = 0.5 * (r.y_left + r.y_right); + r.value = TestFunction(cx, cy); + + const double dx = r.x_right - r.x_left; + const double dy = r.y_right - r.y_left; + r.characteristic = r.value - L * std::sqrt(dx * dx + dy * dy); + + if (r.characteristic > local_best) { + local_best = r.characteristic; + local_index = i; + } + } + + struct { + double value; + int index; + } local, global; + + local.value = local_best; + local.index = local_index; + + MPI_Allreduce(&local, &global, 1, MPI_DOUBLE_INT, MPI_MAXLOC, + MPI_COMM_WORLD); + + if (rank == 0 && global.index >= 0) { + Rect best = rects[global.index]; + rects.erase(rects.begin() + global.index); + + const double mx = 0.5 * (best.x_left + best.x_right); + const double my = 0.5 * (best.y_left + best.y_right); + + rects.emplace_back(best.x_left, mx, best.y_left, my); + rects.emplace_back(mx, best.x_right, best.y_left, my); + rects.emplace_back(best.x_left, mx, my, best.y_right); + rects.emplace_back(mx, best.x_right, my, best.y_right); + } + + MPI_Barrier(MPI_COMM_WORLD); + } + + if (rank == 0) { + double best_val = rects.front().value; + for (const auto& r : rects) { + best_val = std::min(best_val, r.value); + } + GetOutput() = best_val; + } + + return true; +} + +bool LikhanovMMultiStepSchemeMPI::PostProcessingImpl() { + return std::isfinite(GetOutput()); +} + +} // namespace likhanov_m_multi_step_scheme diff --git a/tasks/likhanov_m_multi_step_scheme/report.md b/tasks/likhanov_m_multi_step_scheme/report.md new file mode 100644 index 0000000000..e8240217fc --- /dev/null +++ b/tasks/likhanov_m_multi_step_scheme/report.md @@ -0,0 +1,262 @@ +# Реализация гиперкуба + +- Студент: Лиханов Матвей Дмитриевич, группа 3823Б1ПР1 +- Технология: MPI + SEQ +- Вариант: 10 + +## Введение + +Данный проект посвящён реализации многошаговой схемы решения двумерных задач глобальной оптимизации с использованием последовательного (SEQ) и параллельного (MPI) подходов. Основная цель — ускорение вычислений за счёт распараллеливания расчёта характеристик прямоугольников. + +## Постановка задачи + +### Формулировка + +Дано: +- Функция двух переменных f(x, y), которую необходимо минимизировать. +- Начальная область поиска в виде прямоугольника. + +Требуется: +- Найти приближённое глобальное минимальное значение функции. +- Использовать многошаговую схему разбиения прямоугольников и оценку характеристик для выбора области с наибольшей потенциальной эффективностью. + +### Ограничения + +- Функция f(x, y) задана аналитически. +- Размер области и количество шагов фиксированы. +- Для MPI: обмен данными ограничен минимальными необходимыми сведениями о характеристиках прямоугольников. + +## Описание алгоритма (последовательная версия) + +Последовательная версия алгоритма выполняется одним процессом без распараллеливания. Основные шаги: + +1. Инициализация: + - Задается начальный прямоугольник `[0,1] x [0,1]`. + - Определяется число шагов `steps` для итераций. + +2. Итеративное вычисление характеристик: + - На каждом шаге для каждого прямоугольника вычисляется: + - Центр прямоугольника `(cx, cy)`. + - Значение функции `f(cx, cy)` и характеристика: + ``` + characteristic = value - L * sqrt(dx^2 + dy^2) + ``` + - Здесь `dx` и `dy` — размеры прямоугольника по осям `x` и `y`. + +3. Выбор лучшего прямоугольника: + - На каждом шаге выбирается прямоугольник с максимальной характеристикой. + +4. Разбиение прямоугольника: + - Лучший прямоугольник делится на четыре меньших прямоугольника. + - Эти новые прямоугольники добавляются в список для следующей итерации. + +5. Повторение шагов 2–4 до достижения заданного числа шагов. + +6. Получение результата: + - После всех шагов выбирается минимальное значение функции среди всех прямоугольников как приближённый глобальный минимум. + +### Реализация +```cpp +bool LikhanovMMultiStepSchemeSEQ::RunImpl() { + const int steps = GetInput(); + if (steps <= 0) { + return false; + } + + std::vector rects; + rects.emplace_back(0.0, 1.0, 0.0, 1.0); + + const double L = 2.0; + + for (int step = 0; step < steps; step++) { + for (auto& r : rects) { + const double cx = 0.5 * (r.x_left + r.x_right); + const double cy = 0.5 * (r.y_left + r.y_right); + r.value = TestFunction(cx, cy); + + const double dx = r.x_right - r.x_left; + const double dy = r.y_right - r.y_left; + r.characteristic = r.value - L * std::sqrt(dx * dx + dy * dy); + } + + auto best_it = std::max_element( + rects.begin(), rects.end(), + [](const Rect& a, const Rect& b) { + return a.characteristic < b.characteristic; + }); + + Rect best = *best_it; + rects.erase(best_it); + + const double mx = 0.5 * (best.x_left + best.x_right); + const double my = 0.5 * (best.y_left + best.y_right); + + rects.emplace_back(best.x_left, mx, best.y_left, my); + rects.emplace_back(mx, best.x_right, best.y_left, my); + rects.emplace_back(best.x_left, mx, my, best.y_right); + rects.emplace_back(mx, best.x_right, my, best.y_right); + } + + double best_val = rects.front().value; + for (const auto& r : rects) { + best_val = std::min(best_val, r.value); + } + + GetOutput() = best_val; + return true; +} +``` + +## Схема распараллеливания (MPI) + +Алгоритм основан на итеративном разбиении прямоугольников области поиска и вычислении их характеристик для выбора перспективной области. Основные шаги алгоритма: + +1. Инициализация: + - Задаётся начальный прямоугольник `[0,1] x [0,1]`. + - Определяется число шагов `steps` для итераций. + +2. Распараллеливание: + - Каждый процесс MPI получает часть прямоугольников по принципу round-robin: + ``` + for i = rank; i < rects.size(); i += size + ``` + - Каждый процесс вычисляет значение функции `f(x,y)` в центре прямоугольника и его характеристику: + ``` + characteristic = value - L * sqrt(dx^2 + dy^2) + ``` + +3. Выбор лучшего прямоугольника: + - На каждом процессе определяется локально лучший прямоугольник с максимальной характеристикой. + - С помощью `MPI_Allreduce` с операцией `MPI_MAXLOC` определяется глобально лучший прямоугольник среди всех процессов. + +4. Разбиение прямоугольника: + - Процесс с `rank = 0` делит лучший прямоугольник на четыре меньших и добавляет их в список прямоугольников. + - Синхронизация всех процессов через `MPI_Barrier`. + +5. Повторение шагов 2-4 до достижения заданного числа шагов. + +6. Получение результата: + - Процесс 0 выбирает минимальное значение функции среди всех прямоугольников как приближённый глобальный минимум. + +### Реализация +```cpp +bool LikhanovMMultiStepSchemeMPI::RunImpl() { + int rank = 0; + int size = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + const int steps = GetInput(); + if (steps <= 0) { + return false; + } + + std::vector rects; + rects.emplace_back(0.0, 1.0, 0.0, 1.0); + + const double L = 2.0; + + for (int step = 0; step < steps; step++) { + double local_best = -1e9; + int local_index = -1; + + for (int i = rank; i < static_cast(rects.size()); i += size) { + Rect& r = rects[i]; + + const double cx = 0.5 * (r.x_left + r.x_right); + const double cy = 0.5 * (r.y_left + r.y_right); + r.value = TestFunction(cx, cy); + + const double dx = r.x_right - r.x_left; + const double dy = r.y_right - r.y_left; + r.characteristic = r.value - L * std::sqrt(dx * dx + dy * dy); + + if (r.characteristic > local_best) { + local_best = r.characteristic; + local_index = i; + } + } + + struct { + double value; + int index; + } local, global; + + local.value = local_best; + local.index = local_index; + + MPI_Allreduce(&local, &global, 1, MPI_DOUBLE_INT, MPI_MAXLOC, + MPI_COMM_WORLD); + + if (rank == 0 && global.index >= 0) { + Rect best = rects[global.index]; + rects.erase(rects.begin() + global.index); + + const double mx = 0.5 * (best.x_left + best.x_right); + const double my = 0.5 * (best.y_left + best.y_right); + + rects.emplace_back(best.x_left, mx, best.y_left, my); + rects.emplace_back(mx, best.x_right, best.y_left, my); + rects.emplace_back(best.x_left, mx, my, best.y_right); + rects.emplace_back(mx, best.x_right, my, best.y_right); + } + + MPI_Barrier(MPI_COMM_WORLD); + } + + if (rank == 0) { + double best_val = rects.front().value; + for (const auto& r : rects) { + best_val = std::min(best_val, r.value); + } + GetOutput() = best_val; + } + + return true; +} +``` + +## Экспериментальные результаты + +### Окружение +- Аппаратное обеспечение и операционная система + - Процессор: Apple M3 + - Оперативная память: 8 ГБ + - Хост-операционная система: macOS Tahoe 26.2 +- Инструменты + - Окружение выполнения: Docker-контейнер + - Гостевая ОС контейнера: Ubuntu Linux + - Компилятор: GCC (используемый по умолчанию в контейнере) + - Тип сборки: Release + - MPI: Open MPI + +### Оценка производительности (task_run) +Формула ускорения: `S(P) = Tseq / T(P)`, где базовое время берём от последовательной реализации. + +|Режим| Число процессов | Время, c | Ускорение S(P) | +|-----|-----------------|----------|---------------| +|SEQ| 1 | 0.9157 | 1.00 | +|MPI| 2 | 0.4836 | 1.89 | +|MPI| 4 | 0.3711 | 2.47 | +|MPI| 8 | 0.4742 | 1.93| + +### Анализ результатов + +1. Последовательная версия (SEQ): + - Выполняется на одном процессе. + - Время выполнения 0.9157 с — базовое значение для расчета ускорения. +2. MPI версия: + - При 2 процессах ускорение почти двукратное (1.89×). + - При 4 процессах ускорение растет до 2.47×, что показывает положительный эффект распараллеливания. + - При 8 процессах ускорение падает до 1.93×, что связано с накладными расходами на коммуникации между процессами (MPI_Allreduce и MPI_Barrier). + +## Заключение + +1. **Что сработало** + - Последовательная и параллельная реализации корректно находят приближённый глобальный минимум функции. + - MPI-реализация даёт значительное ускорение по сравнению с SEQ при малом числе шагов. + +2. **Что не сработало / ограничения** + - При слишком большом числе процессов ускорение падает из-за накладных расходов на синхронизацию. + - Алгоритм не масштабируется идеально для мелких задач. + - Используемая схема MPI_Allreduce создаёт узкое место при увеличении числа процессов. diff --git a/tasks/likhanov_m_multi_step_scheme/seq/include/ops_seq.hpp b/tasks/likhanov_m_multi_step_scheme/seq/include/ops_seq.hpp new file mode 100644 index 0000000000..0dd8f16732 --- /dev/null +++ b/tasks/likhanov_m_multi_step_scheme/seq/include/ops_seq.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "likhanov_m_multi_step_scheme/common/include/common.hpp" +#include "task/include/task.hpp" + +namespace likhanov_m_multi_step_scheme { + +class LikhanovMMultiStepSchemeSEQ : public BaseTask { + public: + static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() { + return ppc::task::TypeOfTask::kSEQ; + } + explicit LikhanovMMultiStepSchemeSEQ(const InType &in); + + private: + bool ValidationImpl() override; + bool PreProcessingImpl() override; + bool RunImpl() override; + bool PostProcessingImpl() override; +}; + +} // namespace likhanov_m_multi_step_scheme diff --git a/tasks/likhanov_m_multi_step_scheme/seq/src/ops_seq.cpp b/tasks/likhanov_m_multi_step_scheme/seq/src/ops_seq.cpp new file mode 100644 index 0000000000..b65a32f0c8 --- /dev/null +++ b/tasks/likhanov_m_multi_step_scheme/seq/src/ops_seq.cpp @@ -0,0 +1,79 @@ +#include "likhanov_m_multi_step_scheme/seq/include/ops_seq.hpp" + +#include +#include + +#include "likhanov_m_multi_step_scheme/common/include/common.hpp" +#include "util/include/util.hpp" + +namespace likhanov_m_multi_step_scheme { + +LikhanovMMultiStepSchemeSEQ::LikhanovMMultiStepSchemeSEQ(const InType &in) { + SetTypeOfTask(GetStaticTypeOfTask()); + GetInput() = in; + GetOutput() = 0; +} + +bool LikhanovMMultiStepSchemeSEQ::ValidationImpl() { + return (GetInput() > 0) && (GetOutput() == 0); +} + +bool LikhanovMMultiStepSchemeSEQ::PreProcessingImpl() { + GetOutput() = 2 * GetInput(); + return GetOutput() > 0; +} + +bool LikhanovMMultiStepSchemeSEQ::RunImpl() { + const int steps = GetInput(); + if (steps <= 0) { + return false; + } + + std::vector rects; + rects.emplace_back(0.0, 1.0, 0.0, 1.0); + + const double L = 2.0; + + for (int step = 0; step < steps; step++) { + for (auto& r : rects) { + const double cx = 0.5 * (r.x_left + r.x_right); + const double cy = 0.5 * (r.y_left + r.y_right); + r.value = TestFunction(cx, cy); + + const double dx = r.x_right - r.x_left; + const double dy = r.y_right - r.y_left; + r.characteristic = r.value - L * std::sqrt(dx * dx + dy * dy); + } + + auto best_it = std::max_element( + rects.begin(), rects.end(), + [](const Rect& a, const Rect& b) { + return a.characteristic < b.characteristic; + }); + + Rect best = *best_it; + rects.erase(best_it); + + const double mx = 0.5 * (best.x_left + best.x_right); + const double my = 0.5 * (best.y_left + best.y_right); + + rects.emplace_back(best.x_left, mx, best.y_left, my); + rects.emplace_back(mx, best.x_right, best.y_left, my); + rects.emplace_back(best.x_left, mx, my, best.y_right); + rects.emplace_back(mx, best.x_right, my, best.y_right); + } + + double best_val = rects.front().value; + for (const auto& r : rects) { + best_val = std::min(best_val, r.value); + } + + GetOutput() = best_val; + return true; +} + +bool LikhanovMMultiStepSchemeSEQ::PostProcessingImpl() { + return std::isfinite(GetOutput()); +} + +} // namespace likhanov_m_multi_step_scheme diff --git a/tasks/likhanov_m_multi_step_scheme/settings.json b/tasks/likhanov_m_multi_step_scheme/settings.json new file mode 100644 index 0000000000..b1a0d52574 --- /dev/null +++ b/tasks/likhanov_m_multi_step_scheme/settings.json @@ -0,0 +1,7 @@ +{ + "tasks_type": "processes", + "tasks": { + "mpi": "enabled", + "seq": "enabled" + } +} diff --git a/tasks/likhanov_m_multi_step_scheme/tests/.clang-tidy b/tasks/likhanov_m_multi_step_scheme/tests/.clang-tidy new file mode 100644 index 0000000000..ef43b7aa8a --- /dev/null +++ b/tasks/likhanov_m_multi_step_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/likhanov_m_multi_step_scheme/tests/functional/main.cpp b/tasks/likhanov_m_multi_step_scheme/tests/functional/main.cpp new file mode 100644 index 0000000000..76fd68283e --- /dev/null +++ b/tasks/likhanov_m_multi_step_scheme/tests/functional/main.cpp @@ -0,0 +1,64 @@ +#include + +#include +#include +#include +#include + +#include "likhanov_m_multi_step_scheme/common/include/common.hpp" +#include "likhanov_m_multi_step_scheme/mpi/include/ops_mpi.hpp" +#include "likhanov_m_multi_step_scheme/seq/include/ops_seq.hpp" +#include "util/include/func_test_util.hpp" +#include "util/include/util.hpp" + +namespace likhanov_m_multi_step_scheme { + +class LikhanovMMultiStepSchemeRunFuncTests : 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()); + + steps_ = std::get<0>(params); + input_data_ = steps_; + } + + bool CheckTestOutputData(OutType &) final { + return true; + } + + InType GetTestInputData() final { + return input_data_; + } + + private: + InType input_data_{0}; + int steps_{0}; +}; + +namespace { + +TEST_P(LikhanovMMultiStepSchemeRunFuncTests, MultiStepScheme2DGlobalOptimization) { + ExecuteTest(GetParam()); +} + +const std::array kTestParam = {std::make_tuple(3, "steps_3"), std::make_tuple(5, "steps_5"), + std::make_tuple(7, "steps_7")}; + +const auto kTestTasksList = std::tuple_cat( + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_likhanov_m_multi_step_scheme), + ppc::util::AddFuncTask(kTestParam, PPC_SETTINGS_likhanov_m_multi_step_scheme)); + +const auto kGtestValues = ppc::util::ExpandToValues(kTestTasksList); + +const auto kTestName = LikhanovMMultiStepSchemeRunFuncTests::PrintFuncTestName; + +INSTANTIATE_TEST_SUITE_P(LikhanovMMultiStepScheme, LikhanovMMultiStepSchemeRunFuncTests, kGtestValues, kTestName); + +} // namespace + +} // namespace likhanov_m_multi_step_scheme diff --git a/tasks/likhanov_m_multi_step_scheme/tests/performance/main.cpp b/tasks/likhanov_m_multi_step_scheme/tests/performance/main.cpp new file mode 100644 index 0000000000..1268cbd518 --- /dev/null +++ b/tasks/likhanov_m_multi_step_scheme/tests/performance/main.cpp @@ -0,0 +1,42 @@ +#include + +#include "likhanov_m_multi_step_scheme/common/include/common.hpp" +#include "likhanov_m_multi_step_scheme/mpi/include/ops_mpi.hpp" +#include "likhanov_m_multi_step_scheme/seq/include/ops_seq.hpp" +#include "util/include/perf_test_util.hpp" + +namespace likhanov_m_multi_step_scheme { + +class LikhanovMMultiStepSchemeRunPerfTests : public ppc::util::BaseRunPerfTests { + protected: + static constexpr int kSteps_ = 10000; + InType input_data_{}; + + void SetUp() override { + input_data_ = kSteps_; + } + + bool CheckTestOutputData(OutType &) final { + return true; + } + + InType GetTestInputData() final { + return input_data_; + } +}; + +TEST_P(LikhanovMMultiStepSchemeRunPerfTests, RunPerfModes) { + ExecuteTest(GetParam()); +} + +const auto kAllPerfTasks = + ppc::util::MakeAllPerfTasks( + PPC_SETTINGS_likhanov_m_multi_step_scheme); + +const auto kGtestValues = ppc::util::TupleToGTestValues(kAllPerfTasks); + +const auto kPerfTestName = LikhanovMMultiStepSchemeRunPerfTests::CustomPerfTestName; + +INSTANTIATE_TEST_SUITE_P(RunModeTests, LikhanovMMultiStepSchemeRunPerfTests, kGtestValues, kPerfTestName); + +} // namespace likhanov_m_multi_step_scheme