From 0ee816a69c4257d7b2f90d00de3934e3a1c57c60 Mon Sep 17 00:00:00 2001 From: andry300000 Date: Fri, 22 May 2026 20:39:08 +0000 Subject: [PATCH 1/4] v1 --- .../all/report.md | 131 ++++++++++++++ .../omp/report.md | 94 ++++++++++ .../report.md | 156 +++++++++++++++++ .../seq/report.md | 95 ++++++++++ .../settings.json | 2 +- .../stl/report.md | 164 ++++++++++++++++++ .../tbb/report.md | 93 ++++++++++ 7 files changed, 734 insertions(+), 1 deletion(-) create mode 100644 tasks/cheremkhin_a_matr_mult_cannon_alg/all/report.md create mode 100644 tasks/cheremkhin_a_matr_mult_cannon_alg/omp/report.md create mode 100644 tasks/cheremkhin_a_matr_mult_cannon_alg/report.md create mode 100644 tasks/cheremkhin_a_matr_mult_cannon_alg/seq/report.md create mode 100644 tasks/cheremkhin_a_matr_mult_cannon_alg/stl/report.md create mode 100644 tasks/cheremkhin_a_matr_mult_cannon_alg/tbb/report.md diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/all/report.md b/tasks/cheremkhin_a_matr_mult_cannon_alg/all/report.md new file mode 100644 index 0000000000..62907ff05f --- /dev/null +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/all/report.md @@ -0,0 +1,131 @@ +# Умножение плотных матриц. Элементы типа double. Блочная схема, алгоритм Кэннона. — ALL + +- Student: Черемхин Андрей Александрович +- Technology: ALL +- Variant: 1 + +## 1. Контекст + +ALL-версия реализует гибридную схему: распределение блоков между MPI-процессами и внутрипроцессное ускорение локальных операций с помощью OpenMP. + +## 2. Постановка задачи + +На вход подаётся размер n и две матрицы A, B размера n x n в одномерном построчном виде. Требуется получить матрицу C = A * B. Валидация принимает только положительный n и два массива длины n * n. + +Если n не совпадает с удобным блочным размером, реализация создаёт расширенные матрицы np x np и заполняет дополнительную область нулями. В финальный ответ копируется только исходная область n x n. + +## 3. Базовый алгоритм + +Матрица дополняется нулями до размера `padded_n`, который делится на размер виртуальной сетки. Каждый виртуальный узел сетки хранит блоки `A`, `B` и локальный блок результата `C`. На каждом шаге выполняется локальное умножение блоков, затем блоки `A` и `B` циклически сдвигаются по строкам и столбцам виртуальной сетки. + +## 4. Межпроцессная схема + +Реализация получает `world_rank` и `world_size` через MPI: + +```cpp +MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); +MPI_Comm_size(MPI_COMM_WORLD, &world_size); +``` + +Размер виртуальной сетки выбирается как минимальный `q`, для которого `q * q >= world_size`. Если виртуальных ячеек больше, чем реальных MPI-процессов, они распределяются циклически: + +```cpp +int GetOwnerRank(int virtual_rank, int world_size) { + return virtual_rank % world_size; +} +``` + +Rank 0 подготавливает padded-матрицы, извлекает из них начально сдвинутые блоки алгоритма Кэннона и отправляет их владельцам через `MPI_Send`. Остальные rank-и принимают свои блоки через `MPI_Recv`. + +## 5. Внутрипроцессная схема + +Внутри процесса используются OpenMP-циклы с `schedule(static)` для: + +- копирования глобальной матрицы в padded-представление; +- извлечения и вставки локальных блоков; +- локального умножения блоков в `MulAddLocal`; +- копирования результата из padded-представления. + +Число потоков задаётся через `PPC_NUM_THREADS` и применяется вызовом `omp_set_num_threads(requested_threads)`. + +## 6. Детали реализации + +Файлы реализации: `all/include/ops_all.hpp`, `all/src/ops_all.cpp`. + +Основной цикл: + +```cpp +for (int step = 0; step < q; ++step) { + for (auto &cell : local_cells) { + MulAddLocal(cell.a, cell.b, cell.c, block_n); + } + if (step + 1 < q) { + ShiftBlocksCannon(local_cells, owner_by_rank, block_n, q, world_rank); + } +} +``` + +Сдвиги выполняются в `ExchangePhase`. Если источник блока находится на том же MPI-процессе, выполняется локальное копирование. Если владелец другой, используются неблокирующие `MPI_Irecv` и `MPI_Isend`, затем `MPI_Waitall` для завершения обмена. + +После вычисления `GatherResultBlocks` собирает блоки `C` на rank 0. Затем `MPI_Bcast` рассылает полную padded-матрицу результата всем rank-ам, чтобы каждый экземпляр задачи мог сформировать одинаковый `OutType`. + +## 7. Проверка корректности + +Корректность проверяется сравнением с oracle на тех же входах, что и у остальных backend-ов. Для гибридной версии важно запускать тесты при нескольких значениях `PPC_NUM_PROC`, потому что распределение виртуальных ячеек по владельцам меняется. + +Команда: + +```bash +cmake -S . -B build -D USE_COVERAGE=ON -D CMAKE_EXPORT_COMPILE_COMMANDS=ON -D CMAKE_BUILD_TYPE=Releas +cmake --build build -j --parallel +export OMPI_ALLOW_RUN_AS_ROOT=1 +export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 + +# ranks = 2, threads per rank = 1, total workers = 2 +PPC_NUM_PROC=2 PPC_NUM_THREADS=1 \ +mpirun --oversubscribe -x PPC_NUM_PROC -x PPC_NUM_THREADS -n 2 \ + ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_all*' + +# ranks = 2, threads per rank = 2, total workers = 4 +PPC_NUM_PROC=2 PPC_NUM_THREADS=2 \ +mpirun --oversubscribe -x PPC_NUM_PROC -x PPC_NUM_THREADS -n 2 \ + ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_all*' + +# ranks = 4, threads per rank = 1, total workers = 4 +PPC_NUM_PROC=4 PPC_NUM_THREADS=1 \ +mpirun --oversubscribe -x PPC_NUM_PROC -x PPC_NUM_THREADS -n 4 \ + ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_all*' +``` + +Каждая команда `ppc_perf_tests` выводит две строки замеров: `task_run` и `pipeline`. Поэтому три конфигурации запуска соответствуют шести строкам таблицы результатов. + +Дополнительно нужно проверять согласованность результата на всех rank-ах после `MPI_Bcast`. + +## 8. Экспериментальная среда + +- OS: Linux 6.6.114.1-microsoft-standard-WSL2 x86_64; +- CPU: AMD Ryzen 5 5600, 6 cores / 12 threads; +- Compiler: GCC 13.3.0 with MPI and OpenMP; +- Build type: `Release`; +- Process count: `PPC_NUM_PROC`; +- Threads per rank: `PPC_NUM_THREADS`; +- Normalization: `total_workers = PPC_NUM_PROC * PPC_NUM_THREADS`. + +## 9. Результаты + + +| size | ranks | threads per rank | total workers | mode | median time, s | speedup vs seq | efficiency | +| ---- | ----- | ---------------- | ------------- | -------- | -------------- | -------------- | ---------- | +| 640 | 2 | 1 | 2 | task | 0.4014975420 | 6.0691 | 3.0346 | +| 640 | 2 | 2 | 4 | task | 1.6332830144 | 1.4919 | 0.3730 | +| 640 | 4 | 1 | 4 | task | 0.2357804242 | 10.3348 | 2.5837 | +| 640 | 2 | 1 | 2 | pipeline | 0.3924683468 | 6.2572 | 3.1286 | +| 640 | 2 | 2 | 4 | pipeline | 1.6682519290 | 1.4720 | 0.3680 | +| 640 | 4 | 1 | 4 | pipeline | 0.2316247760 | 10.6022 | 2.6506 | + + +Ускорение рассчитано как `T_seq / T_all`, а эффективность — как `speedup / total_workers`. Использованный SEQ baseline: `2.4367350032` для `task` и `2.4557342674` для `pipeline`. При интерпретации результатов нужно отделять вычислительный выигрыш от стоимости MPI-обменов. На размере `640 x 640` сдвиги блоков и финальный broadcast могут быть заметной частью времени. + +## 10. Выводы + +Гибридная версия показывает полный вариант алгоритма Кэннона с распределённой сеткой блоков. Она методически наиболее близка к исходной идее алгоритма, но её эффективность зависит от размера матрицы, числа rank-ов, числа потоков внутри rank-а и цены коммуникации. \ No newline at end of file diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/omp/report.md b/tasks/cheremkhin_a_matr_mult_cannon_alg/omp/report.md new file mode 100644 index 0000000000..24d32563c1 --- /dev/null +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/omp/report.md @@ -0,0 +1,94 @@ +# Умножение плотных матриц. Элементы типа double. Блочная схема, алгоритм Кэннона. — OMP + +- Student: Черемхин Андрей Александрович +- Technology: OMP +- Variant: 1 + +## 1. Контекст + +OpenMP-версия переносит последовательную блочную схему на многопоточное исполнение. Параллелятся три регулярные части: копирование входных матриц в padded-буферы, вычисление независимых блоков результата и копирование итоговой области обратно в выходной массив. + +## 2. Постановка задачи + +Постановка совпадает с SEQ: для двух матриц `A` и `B` размера `n x n` требуется вычислить `C = A * B`. Последовательная версия считается эталоном корректности и источником baseline-времени. + +## 3. Базовый алгоритм + +Алгоритм использует виртуальную сетку блоков `q x q`, размер блока `bs = ceil(n / q)` и расширенный размер `np = q * bs`. Каждый блок результата `C[bi, bj]` вычисляется независимо от остальных блоков, поэтому внешний цикл по координатам блоков можно распараллелить. + +## 4. Схема распараллеливания + +Число потоков берётся из `ppc::util::GetNumThreads()` и задаётся вызовом `omp_set_num_threads`. В runner-е это значение управляется переменной `PPC_NUM_THREADS`, которая также передаётся как `OMP_NUM_THREADS`. + +Используются три директивы: + +- `parallel for schedule(static)` для копирования строк входных матриц; +- `parallel for collapse(2) schedule(static)` для распределения пар блоков `(bi, bj)`; +- `parallel for schedule(static)` для копирования строк результата. + +Для основной области `a`, `b`, `c`, `np`, `bs`, `q` являются `shared`. Индексы циклов и локальные переменные внутри `MulAddBlock` приватны по правилам OpenMP. `reduction`, `atomic` и `critical` не требуются, потому что каждая итерация пары `(bi, bj)` записывает только в свой блок `C[bi, bj]`. + +Ключевой фрагмент: + +```cpp +#pragma omp parallel for default(none) collapse(2) schedule(static) shared(a, b, c, np, bs, q, q64) +for (std::int64_t bi = 0; bi < q64; ++bi) { + for (std::int64_t bj = 0; bj < q64; ++bj) { + for (std::size_t step = 0; step < q; ++step) { + const std::size_t bk = (static_cast(bi) + static_cast(bj) + step) % q; + MulAddBlock(a, b, c, np, bs, static_cast(bi), bk, static_cast(bj)); + } + } +} +``` + +`default(none)` делает область данных явной, а `schedule(static)` подходит для равномерной стоимости блоков. В конце каждой `parallel for`-области есть неявный барьер, который безопасен: следующая фаза должна видеть полностью подготовленные буферы. + +## 5. Детали реализации + +Файлы реализации: `omp/include/ops_omp.hpp`, `omp/src/ops_omp.cpp`. + +Относительно SEQ изменены только участки с независимыми циклами. Функция `MulAddBlock` сохраняет локальный характер записи: поток работает с блоком результата, соответствующим его итерации внешнего цикла. Поэтому гонок на `c` нет. + +## 6. Проверка корректности + +Корректность проверяется сравнением с oracle из функциональных тестов и с результатом SEQ на одинаковых входах. Набор включает размеры `1`, `2`, `3`, `4`, `7`, `10`, `15`; допуск сравнения равен `1e-7`. + +Дополнительно для OMP важно запускать тесты при разном числе потоков: + +```bash +cmake -S . -B build -D USE_COVERAGE=ON -D CMAKE_EXPORT_COMPILE_COMMANDS=ON -D CMAKE_BUILD_TYPE=Releas +cmake --build build -j --parallel +export OMPI_ALLOW_RUN_AS_ROOT=1 +export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 +mpirun --oversubscribe -n 1 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_omp*' +mpirun --oversubscribe -n 2 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_omp*' +mpirun --oversubscribe -n 4 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_omp*' +``` + +## 7. Экспериментальная среда + +- OS: Linux 6.6.114.1-microsoft-standard-WSL2 x86_64; +- CPU: AMD Ryzen 5 5600, 6 cores / 12 threads; +- Compiler: GCC 13.3.0 with OpenMP; +- Build type: `Release`; +- Thread control: `PPC_NUM_THREADS`, `OMP_NUM_THREADS`. + +## 8. Результаты + + +| size | threads | mode | median time, s | speedup vs seq | efficiency | +| ---- | ------- | -------- | -------------- | -------------- | ---------- | +| 640 | 1 | task | 0.8571173458 | 2.8429 | 2.8429 | +| 640 | 2 | task | 0.8821170756 | 2.7624 | 1.3812 | +| 640 | 4 | task | 0.9496027210 | 2.5661 | 0.6415 | +| 640 | 1 | pipeline | 0.851617316 | 2.8836 | 2.8836 | +| 640 | 2 | pipeline | 0.8912803404 | 2.7553 | 1.3776 | +| 640 | 4 | pipeline | 0.9369923256 | 2.6209 | 0.6552 | + + +Ускорение рассчитано относительно SEQ baseline: `2.4367350032` для `task` и `2.4557342674` для `pipeline`. + +## 9. Выводы + +OpenMP-реализация является прямым и компактным способом распараллелить блочный алгоритм. Она сохраняет структуру SEQ, не требует ручной синхронизации и должна быть эффективной на достаточно больших матрицах, где стоимость вычисления блоков превышает накладные расходы создания parallel regions. \ No newline at end of file diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/report.md b/tasks/cheremkhin_a_matr_mult_cannon_alg/report.md new file mode 100644 index 0000000000..42013fec8d --- /dev/null +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/report.md @@ -0,0 +1,156 @@ +# Умножение плотных матриц. Элементы типа double. Блочная схема, алгоритм Кэннона. + +- Student: Черемхин Андрей Александрович, group 3823Б1ПР3 +- Variant: 1 +- Task directory: `tasks/cheremkhin_a_matr_mult_cannon_alg` +- Local reports: `seq/report.md`, `omp/report.md`, `tbb/report.md`, `stl/report.md`, `all/report.md` + +## 1. Введение + +Задача состоит в вычислении произведения двух квадратных матриц вещественных чисел. Для сравнения разных моделей параллелизма используется блочная схема алгоритма Кэннона: матрицы дополняются нулями до размера, удобного для разбиения на квадратную сетку блоков, после чего каждый блок результата накапливает сумму произведений соответствующих блоков входных матриц. + +Такая задача хорошо подходит для отчёта по параллельному программированию, потому что основная стоимость сосредоточена в регулярных вычислениях `O(n^3)`, а данные можно делить по независимым блокам результата. При этом для гибридной версии появляется отдельная цена распределения блоков, MPI-обменов и финального сбора результата. + +## 2. Единая постановка задачи + +Входные данные задаются типом `InType = tuple, vector>`: размер `n` и две матрицы `A` и `B` в построчном одномерном представлении. Выходной тип `OutType = vector` содержит матрицу `C = A * B` того же размера и в том же порядке хранения. + +Корректный вход должен удовлетворять условиям: + +- `n > 0`; +- `A.size() == n * n`; +- `B.size() == n * n`. + +Все backend-ы используют одинаковую валидацию. Если размер `n` не делится на выбранный размер блочной сетки, матрицы дополняются нулями до `padded_n`; после вычисления в ответ копируется только исходная область `n x n`. Поэтому дополнение не меняет математический результат. + +## 3. Единая методика эксперимента + +Экспериментальная площадка для воспроизводимости: + +- OS: Linux 6.6.114.1-microsoft-standard-WSL2 x86_64; +- CPU: AMD Ryzen 5 5600 6-Core Processor, 6 физических ядер, 12 аппаратных потоков; +- Compiler: `c++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0`; +- Build type для замеров: `Release`; +- Число потоков задаётся через `PPC_NUM_THREADS`; runner также передаёт это значение в `OMP_NUM_THREADS`; +- Число MPI-процессов для `all` задаётся через `PPC_NUM_PROC`. + +Функциональные тесты используют размеры `1, 2, 3, 4, 7, 10, 15`. Тест производительности формирует две матрицы размера `640 x 640`, заполняет их детерминированными значениями и сравнивает результат каждого backend-а с независимым последовательным oracle из теста. + +Ускорение в отчёте считается как: + +```text +speedup = T_seq / T_backend +efficiency = speedup / workers +``` + +Для `seq` принимается `workers = 1`. Для `omp`, `tbb` и `stl` `workers` означает число потоков. Для `all` основная нормировка должна использовать `total_workers = PPC_NUM_PROC * PPC_NUM_THREADS`. + +## 4. Сводка корректности + +Корректность проверяется двумя способами. Во-первых, каждый backend проходит общие функциональные тесты, где ожидаемый результат считается обычным тройным циклом умножения матриц с допуском `1e-7`. Во-вторых, performance-тест также содержит проверку результата для матрицы `640 x 640`, поэтому замер времени не отделён от контроля корректности. + +Покрытые случаи: + +- минимальная матрица `1 x 1`; +- малые квадратные матрицы, включая размеры, которые не дают идеального деления на блоки; +- средние размеры `7`, `10`, `15`; +- крупный фиксированный размер `640` для производительности. + +Текущий `settings.json` включает `seq`, `omp`, `tbb`, `all` и отключает `stl`. Исходный код `stl` присутствует и описан в локальном отчёте, но для участия в общей сборке и тестовом прогоне его нужно включить в настройках задачи. + +## 5. Агрегированные результаты + +Ниже приведена сводная таблица по результатам из локальных отчётов. Для `seq`, `omp`, `tbb` и `stl` поле `workers / ranks x threads` означает число worker-ов. Для `all` указана гибридная конфигурация `ranks x threads_per_rank`, а эффективность считается по общему числу работников. + +| backend | mode | size | workers / ranks x threads | median time, s | speedup vs seq | efficiency | notes | +|---|---|---:|---|---:|---:|---:|---| +| seq | task | 640 | 1 | 2.4367350032 | 1.0000 | 1.0000 | baseline | +| seq | pipeline | 640 | 1 | 2.4557342674 | 1.0000 | 1.0000 | baseline | +| omp | task | 640 | 1 | 0.8571173458 | 2.8429 | 2.8429 | `collapse(2)`, `schedule(static)` | +| omp | task | 640 | 2 | 0.8821170756 | 2.7624 | 1.3812 | `collapse(2)`, `schedule(static)` | +| omp | task | 640 | 4 | 0.9496027210 | 2.5661 | 0.6415 | `collapse(2)`, `schedule(static)` | +| omp | pipeline | 640 | 1 | 0.8516173160 | 2.8836 | 2.8836 | `collapse(2)`, `schedule(static)` | +| omp | pipeline | 640 | 2 | 0.8912803404 | 2.7553 | 1.3776 | `collapse(2)`, `schedule(static)` | +| omp | pipeline | 640 | 4 | 0.9369923256 | 2.6209 | 0.6552 | `collapse(2)`, `schedule(static)` | +| tbb | task | 640 | 1 | 0.8220091702 | 2.9644 | 2.9644 | `parallel_for`, `blocked_range2d` | +| tbb | task | 640 | 2 | 0.9062373528 | 2.6888 | 1.3444 | `parallel_for`, `blocked_range2d` | +| tbb | task | 640 | 4 | 0.9534718392 | 2.5556 | 0.6389 | `parallel_for`, `blocked_range2d` | +| tbb | pipeline | 640 | 1 | 0.8447707936 | 2.9070 | 2.9070 | `parallel_for`, `blocked_range2d` | +| tbb | pipeline | 640 | 2 | 0.8468019650 | 2.9000 | 1.4500 | `parallel_for`, `blocked_range2d` | +| tbb | pipeline | 640 | 4 | 0.9090972242 | 2.7013 | 0.6753 | `parallel_for`, `blocked_range2d` | +| stl | task | 640 | 1 | 0.8761261350 | 2.7813 | 2.7813 | `std::async`, manual chunks | +| stl | task | 640 | 2 | 0.8964625766 | 2.7182 | 1.3591 | `std::async`, manual chunks | +| stl | task | 640 | 4 | 0.9821715538 | 2.4810 | 0.6202 | `std::async`, manual chunks | +| stl | pipeline | 640 | 1 | 0.8878959066 | 2.7658 | 2.7658 | `std::async`, manual chunks | +| stl | pipeline | 640 | 2 | 0.8812324666 | 2.7867 | 1.3934 | `std::async`, manual chunks | +| stl | pipeline | 640 | 4 | 0.9960843862 | 2.4654 | 0.6163 | `std::async`, manual chunks | +| all | task | 640 | 2 x 1 | 0.4014975420 | 6.0691 | 3.0346 | MPI ranks x OpenMP threads | +| all | task | 640 | 2 x 2 | 1.6332830144 | 1.4919 | 0.3730 | MPI ranks x OpenMP threads | +| all | task | 640 | 4 x 1 | 0.2357804242 | 10.3348 | 2.5837 | MPI ranks x OpenMP threads | +| all | pipeline | 640 | 2 x 1 | 0.3924683468 | 6.2572 | 3.1286 | MPI ranks x OpenMP threads | +| all | pipeline | 640 | 2 x 2 | 1.6682519290 | 1.4720 | 0.3680 | MPI ranks x OpenMP threads | +| all | pipeline | 640 | 4 x 1 | 0.2316247760 | 10.6022 | 2.6506 | MPI ranks x OpenMP threads | +- +## 6. Интерпретация различий + +`seq` задаёт эталон корректности и базовое время. Его основная стоимость — вычисление блочных произведений, а дополнительные расходы связаны с padding и копированием результата. + +`omp` распараллеливает копирование входов, вычисление блоков результата и копирование выхода. Самая важная область — цикл по двум координатам блока результата; `collapse(2)` даёт плоское множество независимых блоков, а `schedule(static)` подходит для регулярной нагрузки. + +`tbb` использует тот же уровень декомпозиции, но отдаёт планирование oneTBB runtime. Конкуренция ограничивается через `global_control::max_allowed_parallelism`, а основное разбиение выполняется через `blocked_range2d`. + +`stl` реализует ручное разбиение диапазона с помощью `std::async`. Эта версия полезна для демонстрации ручного управления worker-задачами; по результатам она близка к OMP/TBB на 1-2 worker-ах, но эффективность снижается при росте числа worker-ов. + +`all` строит виртуальную квадратную сетку MPI-рангов, распределяет блоки между реальными процессами и внутри каждого процесса использует OpenMP для локальной работы с блоками. На малых размерах эта версия может проигрывать из-за рассылки, сдвигов и финального `MPI_Bcast`; выигрыш ожидается только тогда, когда стоимость локального умножения блоков перекрывает коммуникационные расходы. + +## 7. Репродуцируемость + +Базовая сборка: + +```bash +git submodule update --init --recursive --depth=1 +cmake -S . -B build -D USE_FUNC_TESTS=ON -D USE_PERF_TESTS=ON -D CMAKE_BUILD_TYPE=Release +cmake --build build --parallel +``` + +Функциональные тесты потоковых backend-ов: + +```bash +export PPC_NUM_THREADS=4 +export PPC_NUM_PROC=1 +scripts/run_tests.py --running-type=threads --counts 1 2 4 +``` + +Функциональные тесты MPI / hybrid backend-а: + +```bash +export PPC_NUM_THREADS=2 +export PPC_NUM_PROC=2 +scripts/run_tests.py --running-type=processes --counts 2 4 +``` + +Тесты производительности: + +```bash +export PPC_NUM_THREADS=4 +export PPC_NUM_PROC=2 +scripts/run_tests.py --running-type=performance +``` + +Для стабильных чисел следует выполнить несколько повторов, отделить прогрев от основного измерения и занести в таблицу медиану. Если используется `stl`, перед сборкой нужно включить его в `settings.json`. + +## 8. Заключение + +Алгоритм Кэннона в этой задаче представлен как общий блочный baseline и несколько реализаций одной вычислительной схемы. Наиболее ожидаемый выигрыш должны дать `omp` и `tbb`, потому что они распараллеливают независимые блоки результата без межпроцессной коммуникации. Гибридная версия методически сложнее и полезна для демонстрации распределённой схемы, но её эффективность зависит от соотношения размера матрицы и цены MPI-обменов. + +## 9. Источники + +- Методические материалы курса по отчётам и структуре задач. +- OpenMP API Specification. +- oneAPI Threading Building Blocks documentation. +- MPI Standard, MPI Forum. +- C++ reference documentation for `std::async` and futures. + +## 10. Приложение + + diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/seq/report.md b/tasks/cheremkhin_a_matr_mult_cannon_alg/seq/report.md new file mode 100644 index 0000000000..51869480ba --- /dev/null +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/seq/report.md @@ -0,0 +1,95 @@ +# Умножение плотных матриц. Элементы типа double. Блочная схема, алгоритм Кэннона. — SEQ + +- Student: Черемхин Андрей Александрович +- Technology: SEQ +- Variant: 1 + +## 1. Контекст + +Последовательная версия является эталоном для всех остальных backend-ов. Она реализует блочное умножение квадратных матриц, по алгоритму Кэннона. + +## 2. Постановка задачи + +На вход подаётся размер `n` и две матрицы `A`, `B` размера `n x n` в одномерном построчном виде. Требуется получить матрицу `C = A * B`. Валидация принимает только положительный `n` и два массива длины `n * n`. + +Если `n` не совпадает с удобным блочным размером, реализация создаёт расширенные матрицы `np x np` и заполняет дополнительную область нулями. В финальный ответ копируется только исходная область `n x n`. + +## 3. Базовый алгоритм + +1. Выбрать размер виртуальной блочной сетки `q = floor(sqrt(n))`, но не меньше `1`. +2. Вычислить размер блока `bs = ceil(n / q)` и расширенный размер `np = q * bs`. +3. Скопировать входные матрицы в расширенные буферы `a` и `b`. +4. Для каждой пары блочных координат `(bi, bj)` накопить сумму произведений блоков. +5. Скопировать область `n x n` из расширенной матрицы результата в выходной вектор. + +Временная сложность: `O(n^3)` с дополнительной ценой padding до `np`. +Память: `O(np^2)` для трёх расширенных матриц. + +Инвариант корректности: после завершения внутреннего цикла по `step` блок `C[bi, bj]` содержит сумму всех произведений блоков строки `bi` матрицы `A` и столбца `bj` матрицы `B`. + +## 4. Детали реализации + +Файлы реализации: `seq/include/ops_seq.hpp`, `seq/src/ops_seq.cpp`. + +`ValidationImpl` проверяет размерность входа. +`PreProcessingImpl` очищает выходной буфер. +`RunImpl` выполняет всё вычисление, включая padding, блочное умножение и копирование ответа. +`PostProcessingImpl` не задействован. + +Ключевой фрагмент: + +```cpp +for (std::size_t bi = 0; bi < q; ++bi) { + for (std::size_t bj = 0; bj < q; ++bj) { + for (std::size_t step = 0; step < q; ++step) { + const std::size_t bk = (bj + bi + step) % q; + MulAddBlock(a, b, c, np, bs, bi, bk, bj); + } + } +} +``` + +Функция `MulAddBlock` умножает один блок `A[bi, bk]` на один блок `B[bk, bj]` и добавляет результат в блок `C[bi, bj]`. В последовательной версии гонок быть не может, так как все блоки обрабатываются одним потоком. + +## 5. Проверка корректности + +Функциональные тесты сравнивают результат с независимым обычным умножением матриц. Используются размеры `1`, `2`, `3`, `4`, `7`, `10`, `15`, что покрывает минимальный случай и размеры, где padding действительно влияет на внутреннее представление. + +Примеры характерных проверок: + +- `1 x 1`: проверяет минимально допустимый вход; +- `3 x 3` и `7 x 7`: проверяют работу с неполной блочной сеткой; +- `15 x 15`: проверяет больший детерминированный случай. + +## 6. Экспериментальная среда + +- OS: Linux 6.6.114.1-microsoft-standard-WSL2 x86_64; +- CPU: AMD Ryzen 5 5600, 6 cores / 12 threads; +- Compiler: GCC 13.3.0; +- Build type: `Release`; +- Worker count for baseline: `1`. + +Команда запуска: + +```bash +cmake -S . -B build -D USE_COVERAGE=ON -D CMAKE_EXPORT_COMPILE_COMMANDS=ON -D CMAKE_BUILD_TYPE=Releas +cmake --build build -j --parallel +export OMPI_ALLOW_RUN_AS_ROOT=1 +export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 +mpirun --oversubscribe -n 1 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_seq*' +``` + +## 7. Результаты + + +| size | workers | mode | median time, s | comment | +| ---- | ------- | -------- | -------------- | ---------------------------- | +| 640 | 1 | task | 2.4367350032 | baseline для ускорения | +| 640 | 1 | pipeline | 2.4557342674 | baseline для pipeline-режима | + + +Значения получены локальным запуском `ppc_perf_tests` с фильтром `*cheremkhin_a_matr_mult_cannon_alg_seq*`. Именно эти значения используются как знаменатель при вычислении ускорения остальных backend-ов. + +## 8. Выводы + +SEQ-версия задаёт корректный baseline и фиксирует общую блочную схему алгоритма. Она не даёт ускорения, но позволяет честно сравнивать все параллельные реализации с одним и тем же способом подготовки данных и одним критерием корректности. \ No newline at end of file diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json b/tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json index 3f9fcd7950..0be0208fc6 100644 --- a/tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json @@ -3,7 +3,7 @@ "all": "enabled", "omp": "enabled", "seq": "enabled", - "stl": "disabled", + "stl": "enabled", "tbb": "enabled" }, "tasks_type": "threads" diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/stl/report.md b/tasks/cheremkhin_a_matr_mult_cannon_alg/stl/report.md new file mode 100644 index 0000000000..177023369a --- /dev/null +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/stl/report.md @@ -0,0 +1,164 @@ +# Умножение плотных матриц. Элементы типа double. Блочная схема, алгоритм Кэннона. — STL + +- Student: Черемхин Андрей Александрович +- Technology: STL +- Variant: 1 + +## 1. Контекст + +STL-версия нужна для проверки того, насколько хорошо блочный алгоритм Кэннона переносится на ручное управление асинхронными задачами стандартной библиотеки C++. В отличие от OpenMP и TBB, здесь нет внешнего планировщика циклов: код сам выбирает число worker-задач, делит диапазон на части и ожидает завершения каждой части. + +В реализации используется `std::async` с политикой `std::launch::async` и набор `std::future`. Это не скрытая OpenMP/TBB-версия, а backend на средствах стандартной библиотеки: запуск задач, хранение futures и ожидание результата описаны явно в `stl/src/ops_stl.cpp`. + +## 2. Постановка задачи + +На вход подаются размер `n` и две квадратные матрицы `A` и `B`, сохранённые в одномерных векторах в построчном порядке. Требуется вычислить матрицу `C = A * B` и вернуть её в таком же формате. + +Вход считается корректным, если: + +- `n > 0`; +- размер `A` равен `n * n`; +- размер `B` равен `n * n`. + +Последовательная версия `seq` используется как baseline по корректности и времени. Для размеров, которые неудобно делятся на блоки, входные матрицы дополняются нулями до размера `np x np`; после вычисления в выходной вектор копируется только исходная область `n x n`. + +## 3. Базовый алгоритм + +Алгоритм совпадает с общей блочной схемой задачи. + +1. Вычисляется размер виртуальной сетки `q = floor(sqrt(n))`, но не меньше `1`. +2. Вычисляется размер блока `bs = ceil(n / q)` и расширенный размер `np = q * bs`. +3. Матрицы `A` и `B` копируются в padded-буферы `a` и `b`. +4. Для каждого блока результата `C[bi, bj]` выполняется накопление произведений блоков `A[bi, bk] * B[bk, bj]`. +5. Из padded-матрицы результата копируется исходная область `n x n`. + +Временная сложность вычислительного ядра остаётся `O(n^3)`. Дополнительная память — `O(np^2)` для padded-матриц `a`, `b` и `c`. Корректность блочной схемы основана на том, что каждый блок результата после цикла по `step` содержит сумму всех нужных блочных произведений. + +## 4. Схема распараллеливания + +Повторяющаяся схема "параллельный цикл по независимым индексам" вынесена во вспомогательную функцию `ParallelFor`. Она принимает количество итераций `count`, запрошенное число worker-ов и функцию обработки одного индекса. + +Число worker-ов выбирается так: + +```cpp +const std::size_t workers = std::max(1, std::min(count, requested_threads)); +``` + +Это защищает от запуска большего числа задач, чем есть независимых итераций. Если worker только один, функция выполняет обычный последовательный цикл без создания `std::async`. + +Для нескольких worker-ов диапазон делится на чанки размера `ceil(count / workers)`. Каждая асинхронная задача получает полуинтервал `[begin, end)`: + +```cpp +const std::size_t chunk = CeilDiv(count, workers); +std::vector> tasks; +tasks.reserve(workers); + +for (std::size_t worker = 0; worker < workers; ++worker) { + const std::size_t begin = worker * chunk; + const std::size_t end = std::min(begin + chunk, count); + if (begin >= end) { + break; + } + + tasks.emplace_back(std::async(std::launch::async, [begin, end, &fn] { + for (std::size_t idx = begin; idx < end; ++idx) { + fn(idx); + } + })); +} +``` + +Ожидание выполняется отдельным циклом после запуска всех задач: + +```cpp +for (auto &task : tasks) { + task.get(); +} +``` + +Это важный момент для STL-версии: если ожидать каждую задачу сразу после создания, работа фактически сериализуется. В текущей реализации сначала создаются все асинхронные задачи, а затем вызывается `get()`, поэтому независимые чанки могут выполняться параллельно. + +## 5. Детали реализации + +Файлы реализации: `stl/include/ops_stl.hpp`, `stl/src/ops_stl.cpp`. + +`ValidationImpl` проверяет размерность входных матриц. `PreProcessingImpl` очищает выходной буфер. Основная работа находится в `RunImpl`; `PostProcessingImpl` дополнительных действий не выполняет. + +`ParallelFor` используется в трёх местах: + +- копирование строк исходных матриц в padded-буферы; +- вычисление `q * q` независимых блоков результата; +- копирование строк результата из padded-матрицы в выходной вектор. + +Основная параллельная область выглядит так: + +```cpp +ParallelFor(q * q, threads, [&](std::size_t block_idx) { + const std::size_t bi = block_idx / q; + const std::size_t bj = block_idx % q; + + for (std::size_t step = 0; step < q; ++step) { + const std::size_t bk = (bi + bj + step) % q; + MulAddBlock(a, b, c, np, bs, bi, bk, bj); + } +}); +``` + +Каждая итерация `block_idx` соответствует одному блоку `C[bi, bj]`. Поэтому две разные worker-задачи не записывают в один и тот же блок результата. Матрицы `a` и `b` после фазы копирования только читаются. Благодаря этому в реализации не нужны `mutex`, `atomic` или другие примитивы защиты записи. + +## 6. Проверка корректности + +Корректность проверяется общими функциональными тестами проекта. Для каждого теста ожидаемый результат считается независимым тройным циклом обычного умножения матриц, а затем сравнивается с результатом STL-версии с допуском `1e-7`. + +Покрытые размеры: `1`, `2`, `3`, `4`, `7`, `10`, `15`. Они проверяют минимальный случай, малые матрицы и размеры, при которых padding влияет на внутреннее представление. + +Команда запуска функциональных тестов: + +```bash +export PPC_NUM_PROC=1 +scripts/run_tests.py --running-type=threads --counts 1 2 4 +``` + +Для производительности используется общий тест на матрицах `640 x 640`; он запускает оба режима performance-инфраструктуры: `task` и `pipeline`. + +## 7. Экспериментальная среда + +- OS: Linux 6.6.114.1-microsoft-standard-WSL2 x86_64; +- CPU: AMD Ryzen 5 5600, 6 cores / 12 threads; +- Compiler: GCC 13.3.0; +- Build type: `Release`; +- Worker count source: `PPC_NUM_THREADS`; +- SEQ baseline для расчёта ускорения: `2.4367350032` для `task` и `2.4557342674` для `pipeline`. + +Команды сборки и запуска: + +```bash +cmake -S . -B build -D USE_COVERAGE=ON -D CMAKE_EXPORT_COMPILE_COMMANDS=ON -D CMAKE_BUILD_TYPE=Releas +cmake --build build -j --parallel +export OMPI_ALLOW_RUN_AS_ROOT=1 +export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 +mpirun --oversubscribe -n 1 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_stl*' +mpirun --oversubscribe -n 2 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_stl*' +mpirun --oversubscribe -n 4 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_stl*' +``` + +## 8. Результаты + + +| size | workers | mode | median time, s | speedup vs seq | efficiency | +| ---- | ------- | -------- | -------------- | -------------- | ---------- | +| 640 | 1 | task | 0.8761261350 | 2.7813 | 2.7813 | +| 640 | 2 | task | 0.8964625766 | 2.7182 | 1.3591 | +| 640 | 4 | task | 0.9821715538 | 2.4810 | 0.6202 | +| 640 | 1 | pipeline | 0.8878959066 | 2.7658 | 2.7658 | +| 640 | 2 | pipeline | 0.8812324666 | 2.7867 | 1.3934 | +| 640 | 4 | pipeline | 0.9960843862 | 2.4654 | 0.6163 | + + +Ускорение рассчитано как `T_seq / T_stl`, а эффективность — как `speedup / workers`. Использованный SEQ baseline: `2.4367350032` для `task` и `2.4557342674` для `pipeline`. Для STL-версии отдельно важно интерпретировать цену создания асинхронных задач и ожидания `future::get()`: на малых или плохо гранулированных диапазонах эти накладные расходы могут съесть выигрыш от параллельного вычисления блоков. + +## 9. Выводы + +STL-реализация сохраняет общую блочную схему алгоритма Кэннона, но вручную управляет разбиением работы. Её сильная сторона — прозрачная карта "worker-задача → диапазон индексов"; слабая сторона — отсутствие специализированного runtime-планировщика и возможные накладные расходы `std::async`. + +С точки зрения корректности версия безопасна: запись разделена по строкам или по независимым блокам результата, а чтение общих padded-матриц начинается только после завершения фазы их заполнения. Поэтому дополнительные блокировки в текущей схеме не требуются. \ No newline at end of file diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/tbb/report.md b/tasks/cheremkhin_a_matr_mult_cannon_alg/tbb/report.md new file mode 100644 index 0000000000..e972d540e0 --- /dev/null +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/tbb/report.md @@ -0,0 +1,93 @@ +# Умножение плотных матриц. Элементы типа double. Блочная схема, алгоритм Кэннона. — TBB + +- Student: Черемхин Андрей Александрович +- Technology: TBB +- Variant: 1 + +## 1. Контекст + +oneTBB-версия использует ту же блочную декомпозицию, что и SEQ/OMP, но планирование независимых работ передаётся runtime-библиотеке TBB. Это позволяет описать работу как набор диапазонов, а не вручную управлять потоками. + +## 2. Постановка задачи + +Требуется вычислить произведение двух квадратных матриц `C = A * B`. Вход и выход совпадают с общей постановкой: `n`, вектор `A`, вектор `B`, результат в построчном одномерном виде. + +## 3. Базовый алгоритм + +Алгоритм выбирает `q`, `bs` и `np`, копирует данные в padded-буферы, вычисляет блоки результата и копирует ответ обратно. Независимость блоков `C[bi, bj]` позволяет использовать двумерный диапазон TBB. + +## 4. Схема распараллеливания + +Конкуренция ограничивается объектом: + +```cpp +oneapi::tbb::global_control control(oneapi::tbb::global_control::max_allowed_parallelism, requested_threads); +``` + +Здесь `requested_threads` берётся из `PPC_NUM_THREADS`. Это ограничивает максимальный параллелизм TBB runtime на время выполнения `RunImpl`. + +Для копирования входа и выхода используется `oneapi::tbb::parallel_for` по строкам. Основное вычисление использует `oneapi::tbb::blocked_range2d(0, q64, 0, q64)`: + +```cpp +oneapi::tbb::parallel_for(oneapi::tbb::blocked_range2d(0, q64, 0, q64), + [&](const oneapi::tbb::blocked_range2d &range) { + for (std::int64_t bi = range.rows().begin(); bi != range.rows().end(); ++bi) { + for (std::int64_t bj = range.cols().begin(); bj != range.cols().end(); ++bj) { + for (std::size_t step = 0; step < q; ++step) { + const std::size_t bk = (static_cast(bi) + static_cast(bj) + step) % q; + MulAddBlock(a, b, c, np, bs, static_cast(bi), bk, static_cast(bj)); + } + } + } +}); +``` + +Grain size явно не задан, поэтому используется разбиение по умолчанию. Это упрощает код, но оставляет runtime-у право выбирать размер подзадач. Для очень малых `q` накладные расходы TBB могут быть заметны. + +## 5. Детали реализации + +Файлы реализации: `tbb/include/ops_tbb.hpp`, `tbb/src/ops_tbb.cpp`. + +Локальных аккумуляторов не требуется: каждый TBB-task записывает в свой блок `C[bi, bj]`. Общие входные матрицы `a` и `b` только читаются после фазы копирования. Запись в `out` также разделена по строкам, поэтому гонки отсутствуют. + +## 6. Проверка корректности + +TBB-версия проверяется теми же функциональными тестами, что и остальные реализации. Результат сравнивается с независимым последовательным oracle с допуском `1e-7`. + +Команда для потоковых тестов: + +```bash +cmake -S . -B build -D USE_COVERAGE=ON -D CMAKE_EXPORT_COMPILE_COMMANDS=ON -D CMAKE_BUILD_TYPE=Releas +cmake --build build -j --parallel +export OMPI_ALLOW_RUN_AS_ROOT=1 +export OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1 +mpirun --oversubscribe -n 1 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_tbb*' +mpirun --oversubscribe -n 2 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_tbb*' +mpirun --oversubscribe -n 4 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_tbb*' +``` + +## 7. Экспериментальная среда + +- OS: Linux 6.6.114.1-microsoft-standard-WSL2 x86_64; +- CPU: AMD Ryzen 5 5600, 6 cores / 12 threads; +- Compiler: GCC 13.3.0; +- Build type: `Release`; +- TBB concurrency control: `global_control::max_allowed_parallelism`; +- Worker count source: `PPC_NUM_THREADS`. + +## 8. Результаты + +| size | workers | mode | median time, s | speedup vs seq | efficiency | +|---:|---:|---|---:|---:|---:| +| 640 | 1 | task | 0.8220091702 | 2.9644 | 2.9644 | +| 640 | 2 | task | 0.9062373528 | 2.6888 | 1.3444 | +| 640 | 4 | task | 0.9534718392 | 2.5556 | 0.6389 | +| 640 | 1 | pipeline | 0.8447707936 | 2.9070 | 2.9070 | +| 640 | 2 | pipeline | 0.8468019650 | 2.9000 | 1.4500 | +| 640 | 4 | pipeline | 0.9090972242 | 2.7013 | 0.6753 | + +Ускорение рассчитано относительно SEQ baseline: `2.4367350032` для `task` и `2.4557342674` для `pipeline`. По этим данным TBB быстрее последовательной версии, но эффективность снижается при росте числа workers из-за накладных расходов runtime и ограниченной гранулярности блочного разбиения. + +## 9. Выводы + +TBB-реализация хорошо соответствует блочной природе алгоритма: основная работа описывается двумерным диапазоном блоков результата. Главные факторы эффективности — размер `q`, стоимость одного блока и накладные расходы runtime на разбиение диапазона. From 097539b3feb71143f89a3708c4ee1b5312250eae Mon Sep 17 00:00:00 2001 From: andry300000 Date: Mon, 25 May 2026 21:56:06 +0000 Subject: [PATCH 2/4] markdownlint-cli2 fix --- .../all/report.md | 45 +++++++---- .../omp/report.md | 35 ++++++--- .../report.md | 75 ++++++++++++++----- .../seq/report.md | 38 ++++++---- .../stl/report.md | 65 +++++++++++----- .../tbb/report.md | 36 ++++++--- 6 files changed, 203 insertions(+), 91 deletions(-) diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/all/report.md b/tasks/cheremkhin_a_matr_mult_cannon_alg/all/report.md index 62907ff05f..e2307c998e 100644 --- a/tasks/cheremkhin_a_matr_mult_cannon_alg/all/report.md +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/all/report.md @@ -6,17 +6,23 @@ ## 1. Контекст -ALL-версия реализует гибридную схему: распределение блоков между MPI-процессами и внутрипроцессное ускорение локальных операций с помощью OpenMP. +ALL-версия реализует гибридную схему: распределение блоков между MPI-процессами и внутрипроцессное ускорение +локальных операций с помощью OpenMP. ## 2. Постановка задачи -На вход подаётся размер n и две матрицы A, B размера n x n в одномерном построчном виде. Требуется получить матрицу C = A * B. Валидация принимает только положительный n и два массива длины n * n. +На вход подаётся размер n и две матрицы A, B размера n x n в одномерном построчном виде. Требуется получить +матрицу C = A *B. Валидация принимает только положительный n и два массива длины n* n. -Если n не совпадает с удобным блочным размером, реализация создаёт расширенные матрицы np x np и заполняет дополнительную область нулями. В финальный ответ копируется только исходная область n x n. +Если n не совпадает с удобным блочным размером, реализация создаёт расширенные матрицы np x np и заполняет +дополнительную область нулями. В финальный ответ копируется только исходная область n x n. ## 3. Базовый алгоритм -Матрица дополняется нулями до размера `padded_n`, который делится на размер виртуальной сетки. Каждый виртуальный узел сетки хранит блоки `A`, `B` и локальный блок результата `C`. На каждом шаге выполняется локальное умножение блоков, затем блоки `A` и `B` циклически сдвигаются по строкам и столбцам виртуальной сетки. +Матрица дополняется нулями до размера `padded_n`, который делится на размер виртуальной сетки. Каждый +виртуальный узел сетки хранит блоки `A`, `B` и локальный блок результата `C`. На каждом шаге выполняется +локальное умножение блоков, затем блоки `A` и `B` циклически сдвигаются по строкам и столбцам виртуальной +сетки. ## 4. Межпроцессная схема @@ -27,7 +33,8 @@ MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); MPI_Comm_size(MPI_COMM_WORLD, &world_size); ``` -Размер виртуальной сетки выбирается как минимальный `q`, для которого `q * q >= world_size`. Если виртуальных ячеек больше, чем реальных MPI-процессов, они распределяются циклически: +Размер виртуальной сетки выбирается как минимальный `q`, для которого `q * q >= world_size`. Если виртуальных +ячеек больше, чем реальных MPI-процессов, они распределяются циклически: ```cpp int GetOwnerRank(int virtual_rank, int world_size) { @@ -35,7 +42,8 @@ int GetOwnerRank(int virtual_rank, int world_size) { } ``` -Rank 0 подготавливает padded-матрицы, извлекает из них начально сдвинутые блоки алгоритма Кэннона и отправляет их владельцам через `MPI_Send`. Остальные rank-и принимают свои блоки через `MPI_Recv`. +Rank 0 подготавливает padded-матрицы, извлекает из них начально сдвинутые блоки алгоритма Кэннона и отправляет +их владельцам через `MPI_Send`. Остальные rank-и принимают свои блоки через `MPI_Recv`. ## 5. Внутрипроцессная схема @@ -65,13 +73,18 @@ for (int step = 0; step < q; ++step) { } ``` -Сдвиги выполняются в `ExchangePhase`. Если источник блока находится на том же MPI-процессе, выполняется локальное копирование. Если владелец другой, используются неблокирующие `MPI_Irecv` и `MPI_Isend`, затем `MPI_Waitall` для завершения обмена. +Сдвиги выполняются в `ExchangePhase`. Если источник блока находится на том же MPI-процессе, выполняется +локальное копирование. Если владелец другой, используются неблокирующие `MPI_Irecv` и `MPI_Isend`, затем +`MPI_Waitall` для завершения обмена. -После вычисления `GatherResultBlocks` собирает блоки `C` на rank 0. Затем `MPI_Bcast` рассылает полную padded-матрицу результата всем rank-ам, чтобы каждый экземпляр задачи мог сформировать одинаковый `OutType`. +После вычисления `GatherResultBlocks` собирает блоки `C` на rank 0. Затем `MPI_Bcast` рассылает полную +padded-матрицу результата всем rank-ам, чтобы каждый экземпляр задачи мог сформировать одинаковый `OutType`. ## 7. Проверка корректности -Корректность проверяется сравнением с oracle на тех же входах, что и у остальных backend-ов. Для гибридной версии важно запускать тесты при нескольких значениях `PPC_NUM_PROC`, потому что распределение виртуальных ячеек по владельцам меняется. +Корректность проверяется сравнением с oracle на тех же входах, что и у остальных backend-ов. Для гибридной +версии важно запускать тесты при нескольких значениях `PPC_NUM_PROC`, потому что распределение виртуальных +ячеек по владельцам меняется. Команда: @@ -97,7 +110,8 @@ mpirun --oversubscribe -x PPC_NUM_PROC -x PPC_NUM_THREADS -n 4 \ ./build/bin/ppc_perf_tests --gtest_filter='*cheremkhin_a_matr_mult_cannon_alg_all*' ``` -Каждая команда `ppc_perf_tests` выводит две строки замеров: `task_run` и `pipeline`. Поэтому три конфигурации запуска соответствуют шести строкам таблицы результатов. +Каждая команда `ppc_perf_tests` выводит две строки замеров: `task_run` и `pipeline`. Поэтому три конфигурации +запуска соответствуют шести строкам таблицы результатов. Дополнительно нужно проверять согласованность результата на всех rank-ах после `MPI_Bcast`. @@ -113,7 +127,6 @@ mpirun --oversubscribe -x PPC_NUM_PROC -x PPC_NUM_THREADS -n 4 \ ## 9. Результаты - | size | ranks | threads per rank | total workers | mode | median time, s | speedup vs seq | efficiency | | ---- | ----- | ---------------- | ------------- | -------- | -------------- | -------------- | ---------- | | 640 | 2 | 1 | 2 | task | 0.4014975420 | 6.0691 | 3.0346 | @@ -123,9 +136,13 @@ mpirun --oversubscribe -x PPC_NUM_PROC -x PPC_NUM_THREADS -n 4 \ | 640 | 2 | 2 | 4 | pipeline | 1.6682519290 | 1.4720 | 0.3680 | | 640 | 4 | 1 | 4 | pipeline | 0.2316247760 | 10.6022 | 2.6506 | - -Ускорение рассчитано как `T_seq / T_all`, а эффективность — как `speedup / total_workers`. Использованный SEQ baseline: `2.4367350032` для `task` и `2.4557342674` для `pipeline`. При интерпретации результатов нужно отделять вычислительный выигрыш от стоимости MPI-обменов. На размере `640 x 640` сдвиги блоков и финальный broadcast могут быть заметной частью времени. +Ускорение рассчитано как `T_seq / T_all`, а эффективность — как `speedup / total_workers`. Использованный SEQ +baseline: `2.4367350032` для `task` и `2.4557342674` для `pipeline`. При интерпретации результатов нужно +отделять вычислительный выигрыш от стоимости MPI-обменов. На размере `640 x 640` сдвиги блоков и финальный +broadcast могут быть заметной частью времени. ## 10. Выводы -Гибридная версия показывает полный вариант алгоритма Кэннона с распределённой сеткой блоков. Она методически наиболее близка к исходной идее алгоритма, но её эффективность зависит от размера матрицы, числа rank-ов, числа потоков внутри rank-а и цены коммуникации. \ No newline at end of file +Гибридная версия показывает полный вариант алгоритма Кэннона с распределённой сеткой блоков. Она методически +наиболее близка к исходной идее алгоритма, но её эффективность зависит от размера матрицы, числа rank-ов, +числа потоков внутри rank-а и цены коммуникации. diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/omp/report.md b/tasks/cheremkhin_a_matr_mult_cannon_alg/omp/report.md index 24d32563c1..42f244779a 100644 --- a/tasks/cheremkhin_a_matr_mult_cannon_alg/omp/report.md +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/omp/report.md @@ -6,19 +6,25 @@ ## 1. Контекст -OpenMP-версия переносит последовательную блочную схему на многопоточное исполнение. Параллелятся три регулярные части: копирование входных матриц в padded-буферы, вычисление независимых блоков результата и копирование итоговой области обратно в выходной массив. +OpenMP-версия переносит последовательную блочную схему на многопоточное исполнение. Параллелятся три +регулярные части: копирование входных матриц в padded-буферы, вычисление независимых блоков результата и +копирование итоговой области обратно в выходной массив. ## 2. Постановка задачи -Постановка совпадает с SEQ: для двух матриц `A` и `B` размера `n x n` требуется вычислить `C = A * B`. Последовательная версия считается эталоном корректности и источником baseline-времени. +Постановка совпадает с SEQ: для двух матриц `A` и `B` размера `n x n` требуется вычислить `C = A * B`. +Последовательная версия считается эталоном корректности и источником baseline-времени. ## 3. Базовый алгоритм -Алгоритм использует виртуальную сетку блоков `q x q`, размер блока `bs = ceil(n / q)` и расширенный размер `np = q * bs`. Каждый блок результата `C[bi, bj]` вычисляется независимо от остальных блоков, поэтому внешний цикл по координатам блоков можно распараллелить. +Алгоритм использует виртуальную сетку блоков `q x q`, размер блока `bs = ceil(n / q)` и расширенный размер `np += q * bs`. Каждый блок результата `C[bi, bj]` вычисляется независимо от остальных блоков, поэтому внешний цикл +по координатам блоков можно распараллелить. ## 4. Схема распараллеливания -Число потоков берётся из `ppc::util::GetNumThreads()` и задаётся вызовом `omp_set_num_threads`. В runner-е это значение управляется переменной `PPC_NUM_THREADS`, которая также передаётся как `OMP_NUM_THREADS`. +Число потоков берётся из `ppc::util::GetNumThreads()` и задаётся вызовом `omp_set_num_threads`. В runner-е это +значение управляется переменной `PPC_NUM_THREADS`, которая также передаётся как `OMP_NUM_THREADS`. Используются три директивы: @@ -26,7 +32,9 @@ OpenMP-версия переносит последовательную блоч - `parallel for collapse(2) schedule(static)` для распределения пар блоков `(bi, bj)`; - `parallel for schedule(static)` для копирования строк результата. -Для основной области `a`, `b`, `c`, `np`, `bs`, `q` являются `shared`. Индексы циклов и локальные переменные внутри `MulAddBlock` приватны по правилам OpenMP. `reduction`, `atomic` и `critical` не требуются, потому что каждая итерация пары `(bi, bj)` записывает только в свой блок `C[bi, bj]`. +Для основной области `a`, `b`, `c`, `np`, `bs`, `q` являются `shared`. Индексы циклов и локальные переменные +внутри `MulAddBlock` приватны по правилам OpenMP. `reduction`, `atomic` и `critical` не требуются, потому что +каждая итерация пары `(bi, bj)` записывает только в свой блок `C[bi, bj]`. Ключевой фрагмент: @@ -42,17 +50,22 @@ for (std::int64_t bi = 0; bi < q64; ++bi) { } ``` -`default(none)` делает область данных явной, а `schedule(static)` подходит для равномерной стоимости блоков. В конце каждой `parallel for`-области есть неявный барьер, который безопасен: следующая фаза должна видеть полностью подготовленные буферы. +`default(none)` делает область данных явной, а `schedule(static)` подходит для равномерной стоимости блоков. В +конце каждой `parallel for`-области есть неявный барьер, который безопасен: следующая фаза должна видеть +полностью подготовленные буферы. ## 5. Детали реализации Файлы реализации: `omp/include/ops_omp.hpp`, `omp/src/ops_omp.cpp`. -Относительно SEQ изменены только участки с независимыми циклами. Функция `MulAddBlock` сохраняет локальный характер записи: поток работает с блоком результата, соответствующим его итерации внешнего цикла. Поэтому гонок на `c` нет. +Относительно SEQ изменены только участки с независимыми циклами. Функция `MulAddBlock` сохраняет локальный +характер записи: поток работает с блоком результата, соответствующим его итерации внешнего цикла. Поэтому +гонок на `c` нет. ## 6. Проверка корректности -Корректность проверяется сравнением с oracle из функциональных тестов и с результатом SEQ на одинаковых входах. Набор включает размеры `1`, `2`, `3`, `4`, `7`, `10`, `15`; допуск сравнения равен `1e-7`. +Корректность проверяется сравнением с oracle из функциональных тестов и с результатом SEQ на одинаковых +входах. Набор включает размеры `1`, `2`, `3`, `4`, `7`, `10`, `15`; допуск сравнения равен `1e-7`. Дополнительно для OMP важно запускать тесты при разном числе потоков: @@ -76,7 +89,6 @@ mpirun --oversubscribe -n 4 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkh ## 8. Результаты - | size | threads | mode | median time, s | speedup vs seq | efficiency | | ---- | ------- | -------- | -------------- | -------------- | ---------- | | 640 | 1 | task | 0.8571173458 | 2.8429 | 2.8429 | @@ -86,9 +98,10 @@ mpirun --oversubscribe -n 4 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkh | 640 | 2 | pipeline | 0.8912803404 | 2.7553 | 1.3776 | | 640 | 4 | pipeline | 0.9369923256 | 2.6209 | 0.6552 | - Ускорение рассчитано относительно SEQ baseline: `2.4367350032` для `task` и `2.4557342674` для `pipeline`. ## 9. Выводы -OpenMP-реализация является прямым и компактным способом распараллелить блочный алгоритм. Она сохраняет структуру SEQ, не требует ручной синхронизации и должна быть эффективной на достаточно больших матрицах, где стоимость вычисления блоков превышает накладные расходы создания parallel regions. \ No newline at end of file +OpenMP-реализация является прямым и компактным способом распараллелить блочный алгоритм. Она сохраняет +структуру SEQ, не требует ручной синхронизации и должна быть эффективной на достаточно больших матрицах, где +стоимость вычисления блоков превышает накладные расходы создания parallel regions. diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/report.md b/tasks/cheremkhin_a_matr_mult_cannon_alg/report.md index 42013fec8d..b11e41f136 100644 --- a/tasks/cheremkhin_a_matr_mult_cannon_alg/report.md +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/report.md @@ -1,4 +1,4 @@ -# Умножение плотных матриц. Элементы типа double. Блочная схема, алгоритм Кэннона. +# Умножение плотных матриц. Элементы типа double. Блочная схема, алгоритм Кэннона - Student: Черемхин Андрей Александрович, group 3823Б1ПР3 - Variant: 1 @@ -7,13 +7,21 @@ ## 1. Введение -Задача состоит в вычислении произведения двух квадратных матриц вещественных чисел. Для сравнения разных моделей параллелизма используется блочная схема алгоритма Кэннона: матрицы дополняются нулями до размера, удобного для разбиения на квадратную сетку блоков, после чего каждый блок результата накапливает сумму произведений соответствующих блоков входных матриц. +Задача состоит в вычислении произведения двух квадратных матриц вещественных чисел. Для сравнения разных +моделей параллелизма используется блочная схема алгоритма Кэннона: матрицы дополняются нулями до размера, +удобного для разбиения на квадратную сетку блоков, после чего каждый блок результата накапливает сумму +произведений соответствующих блоков входных матриц. -Такая задача хорошо подходит для отчёта по параллельному программированию, потому что основная стоимость сосредоточена в регулярных вычислениях `O(n^3)`, а данные можно делить по независимым блокам результата. При этом для гибридной версии появляется отдельная цена распределения блоков, MPI-обменов и финального сбора результата. +Такая задача хорошо подходит для отчёта по параллельному программированию, потому что основная стоимость +сосредоточена в регулярных вычислениях `O(n^3)`, а данные можно делить по независимым блокам результата. При +этом для гибридной версии появляется отдельная цена распределения блоков, MPI-обменов и финального сбора +результата. ## 2. Единая постановка задачи -Входные данные задаются типом `InType = tuple, vector>`: размер `n` и две матрицы `A` и `B` в построчном одномерном представлении. Выходной тип `OutType = vector` содержит матрицу `C = A * B` того же размера и в том же порядке хранения. +Входные данные задаются типом `InType = tuple, vector>`: размер `n` и две +матрицы `A` и `B` в построчном одномерном представлении. Выходной тип `OutType = vector` содержит +матрицу `C = A * B` того же размера и в том же порядке хранения. Корректный вход должен удовлетворять условиям: @@ -21,7 +29,9 @@ - `A.size() == n * n`; - `B.size() == n * n`. -Все backend-ы используют одинаковую валидацию. Если размер `n` не делится на выбранный размер блочной сетки, матрицы дополняются нулями до `padded_n`; после вычисления в ответ копируется только исходная область `n x n`. Поэтому дополнение не меняет математический результат. +Все backend-ы используют одинаковую валидацию. Если размер `n` не делится на выбранный размер блочной сетки, +матрицы дополняются нулями до `padded_n`; после вычисления в ответ копируется только исходная область `n x n`. +Поэтому дополнение не меняет математический результат. ## 3. Единая методика эксперимента @@ -34,7 +44,9 @@ - Число потоков задаётся через `PPC_NUM_THREADS`; runner также передаёт это значение в `OMP_NUM_THREADS`; - Число MPI-процессов для `all` задаётся через `PPC_NUM_PROC`. -Функциональные тесты используют размеры `1, 2, 3, 4, 7, 10, 15`. Тест производительности формирует две матрицы размера `640 x 640`, заполняет их детерминированными значениями и сравнивает результат каждого backend-а с независимым последовательным oracle из теста. +Функциональные тесты используют размеры `1, 2, 3, 4, 7, 10, 15`. Тест производительности формирует две матрицы +размера `640 x 640`, заполняет их детерминированными значениями и сравнивает результат каждого backend-а с +независимым последовательным oracle из теста. Ускорение в отчёте считается как: @@ -43,11 +55,15 @@ speedup = T_seq / T_backend efficiency = speedup / workers ``` -Для `seq` принимается `workers = 1`. Для `omp`, `tbb` и `stl` `workers` означает число потоков. Для `all` основная нормировка должна использовать `total_workers = PPC_NUM_PROC * PPC_NUM_THREADS`. +Для `seq` принимается `workers = 1`. Для `omp`, `tbb` и `stl` `workers` означает число потоков. Для `all` +основная нормировка должна использовать `total_workers = PPC_NUM_PROC * PPC_NUM_THREADS`. ## 4. Сводка корректности -Корректность проверяется двумя способами. Во-первых, каждый backend проходит общие функциональные тесты, где ожидаемый результат считается обычным тройным циклом умножения матриц с допуском `1e-7`. Во-вторых, performance-тест также содержит проверку результата для матрицы `640 x 640`, поэтому замер времени не отделён от контроля корректности. +Корректность проверяется двумя способами. Во-первых, каждый backend проходит общие функциональные тесты, где +ожидаемый результат считается обычным тройным циклом умножения матриц с допуском `1e-7`. Во-вторых, +performance-тест также содержит проверку результата для матрицы `640 x 640`, поэтому замер времени не отделён +от контроля корректности. Покрытые случаи: @@ -56,14 +72,18 @@ efficiency = speedup / workers - средние размеры `7`, `10`, `15`; - крупный фиксированный размер `640` для производительности. -Текущий `settings.json` включает `seq`, `omp`, `tbb`, `all` и отключает `stl`. Исходный код `stl` присутствует и описан в локальном отчёте, но для участия в общей сборке и тестовом прогоне его нужно включить в настройках задачи. +Текущий `settings.json` включает `seq`, `omp`, `tbb`, `all` и отключает `stl`. Исходный код `stl` присутствует +и описан в локальном отчёте, но для участия в общей сборке и тестовом прогоне его нужно включить в настройках +задачи. ## 5. Агрегированные результаты -Ниже приведена сводная таблица по результатам из локальных отчётов. Для `seq`, `omp`, `tbb` и `stl` поле `workers / ranks x threads` означает число worker-ов. Для `all` указана гибридная конфигурация `ranks x threads_per_rank`, а эффективность считается по общему числу работников. +Ниже приведена сводная таблица по результатам из локальных отчётов. Для `seq`, `omp`, `tbb` и `stl` поле +`workers / ranks x threads` означает число worker-ов. Для `all` указана гибридная конфигурация `ranks x +threads_per_rank`, а эффективность считается по общему числу работников. | backend | mode | size | workers / ranks x threads | median time, s | speedup vs seq | efficiency | notes | -|---|---|---:|---|---:|---:|---:|---| +| --- | --- | ---: | --- | ---: | ---: | ---: | --- | | seq | task | 640 | 1 | 2.4367350032 | 1.0000 | 1.0000 | baseline | | seq | pipeline | 640 | 1 | 2.4557342674 | 1.0000 | 1.0000 | baseline | | omp | task | 640 | 1 | 0.8571173458 | 2.8429 | 2.8429 | `collapse(2)`, `schedule(static)` | @@ -90,18 +110,30 @@ efficiency = speedup / workers | all | pipeline | 640 | 2 x 1 | 0.3924683468 | 6.2572 | 3.1286 | MPI ranks x OpenMP threads | | all | pipeline | 640 | 2 x 2 | 1.6682519290 | 1.4720 | 0.3680 | MPI ranks x OpenMP threads | | all | pipeline | 640 | 4 x 1 | 0.2316247760 | 10.6022 | 2.6506 | MPI ranks x OpenMP threads | + - + ## 6. Интерпретация различий -`seq` задаёт эталон корректности и базовое время. Его основная стоимость — вычисление блочных произведений, а дополнительные расходы связаны с padding и копированием результата. +`seq` задаёт эталон корректности и базовое время. Его основная стоимость — вычисление блочных произведений, а +дополнительные расходы связаны с padding и копированием результата. -`omp` распараллеливает копирование входов, вычисление блоков результата и копирование выхода. Самая важная область — цикл по двум координатам блока результата; `collapse(2)` даёт плоское множество независимых блоков, а `schedule(static)` подходит для регулярной нагрузки. +`omp` распараллеливает копирование входов, вычисление блоков результата и копирование выхода. Самая важная +область — цикл по двум координатам блока результата; `collapse(2)` даёт плоское множество независимых блоков, +а `schedule(static)` подходит для регулярной нагрузки. -`tbb` использует тот же уровень декомпозиции, но отдаёт планирование oneTBB runtime. Конкуренция ограничивается через `global_control::max_allowed_parallelism`, а основное разбиение выполняется через `blocked_range2d`. +`tbb` использует тот же уровень декомпозиции, но отдаёт планирование oneTBB runtime. Конкуренция +ограничивается через `global_control::max_allowed_parallelism`, а основное разбиение выполняется через +`blocked_range2d`. -`stl` реализует ручное разбиение диапазона с помощью `std::async`. Эта версия полезна для демонстрации ручного управления worker-задачами; по результатам она близка к OMP/TBB на 1-2 worker-ах, но эффективность снижается при росте числа worker-ов. +`stl` реализует ручное разбиение диапазона с помощью `std::async`. Эта версия полезна для демонстрации ручного +управления worker-задачами; по результатам она близка к OMP/TBB на 1-2 worker-ах, но эффективность снижается +при росте числа worker-ов. -`all` строит виртуальную квадратную сетку MPI-рангов, распределяет блоки между реальными процессами и внутри каждого процесса использует OpenMP для локальной работы с блоками. На малых размерах эта версия может проигрывать из-за рассылки, сдвигов и финального `MPI_Bcast`; выигрыш ожидается только тогда, когда стоимость локального умножения блоков перекрывает коммуникационные расходы. +`all` строит виртуальную квадратную сетку MPI-рангов, распределяет блоки между реальными процессами и внутри +каждого процесса использует OpenMP для локальной работы с блоками. На малых размерах эта версия может +проигрывать из-за рассылки, сдвигов и финального `MPI_Bcast`; выигрыш ожидается только тогда, когда стоимость +локального умножения блоков перекрывает коммуникационные расходы. ## 7. Репродуцируемость @@ -137,11 +169,16 @@ export PPC_NUM_PROC=2 scripts/run_tests.py --running-type=performance ``` -Для стабильных чисел следует выполнить несколько повторов, отделить прогрев от основного измерения и занести в таблицу медиану. Если используется `stl`, перед сборкой нужно включить его в `settings.json`. +Для стабильных чисел следует выполнить несколько повторов, отделить прогрев от основного измерения и занести в +таблицу медиану. Если используется `stl`, перед сборкой нужно включить его в `settings.json`. ## 8. Заключение -Алгоритм Кэннона в этой задаче представлен как общий блочный baseline и несколько реализаций одной вычислительной схемы. Наиболее ожидаемый выигрыш должны дать `omp` и `tbb`, потому что они распараллеливают независимые блоки результата без межпроцессной коммуникации. Гибридная версия методически сложнее и полезна для демонстрации распределённой схемы, но её эффективность зависит от соотношения размера матрицы и цены MPI-обменов. +Алгоритм Кэннона в этой задаче представлен как общий блочный baseline и несколько реализаций одной +вычислительной схемы. Наиболее ожидаемый выигрыш должны дать `omp` и `tbb`, потому что они распараллеливают +независимые блоки результата без межпроцессной коммуникации. Гибридная версия методически сложнее и полезна +для демонстрации распределённой схемы, но её эффективность зависит от соотношения размера матрицы и цены +MPI-обменов. ## 9. Источники @@ -152,5 +189,3 @@ scripts/run_tests.py --running-type=performance - C++ reference documentation for `std::async` and futures. ## 10. Приложение - - diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/seq/report.md b/tasks/cheremkhin_a_matr_mult_cannon_alg/seq/report.md index 51869480ba..9d31c402e1 100644 --- a/tasks/cheremkhin_a_matr_mult_cannon_alg/seq/report.md +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/seq/report.md @@ -6,13 +6,16 @@ ## 1. Контекст -Последовательная версия является эталоном для всех остальных backend-ов. Она реализует блочное умножение квадратных матриц, по алгоритму Кэннона. +Последовательная версия является эталоном для всех остальных backend-ов. Она реализует блочное умножение +квадратных матриц, по алгоритму Кэннона. ## 2. Постановка задачи -На вход подаётся размер `n` и две матрицы `A`, `B` размера `n x n` в одномерном построчном виде. Требуется получить матрицу `C = A * B`. Валидация принимает только положительный `n` и два массива длины `n * n`. +На вход подаётся размер `n` и две матрицы `A`, `B` размера `n x n` в одномерном построчном виде. Требуется +получить матрицу `C = A * B`. Валидация принимает только положительный `n` и два массива длины `n * n`. -Если `n` не совпадает с удобным блочным размером, реализация создаёт расширенные матрицы `np x np` и заполняет дополнительную область нулями. В финальный ответ копируется только исходная область `n x n`. +Если `n` не совпадает с удобным блочным размером, реализация создаёт расширенные матрицы `np x np` и заполняет +дополнительную область нулями. В финальный ответ копируется только исходная область `n x n`. ## 3. Базовый алгоритм @@ -22,19 +25,18 @@ 4. Для каждой пары блочных координат `(bi, bj)` накопить сумму произведений блоков. 5. Скопировать область `n x n` из расширенной матрицы результата в выходной вектор. -Временная сложность: `O(n^3)` с дополнительной ценой padding до `np`. -Память: `O(np^2)` для трёх расширенных матриц. +Временная сложность: `O(n^3)` с дополнительной ценой padding до `np`. Память: `O(np^2)` для трёх расширенных +матриц. -Инвариант корректности: после завершения внутреннего цикла по `step` блок `C[bi, bj]` содержит сумму всех произведений блоков строки `bi` матрицы `A` и столбца `bj` матрицы `B`. +Инвариант корректности: после завершения внутреннего цикла по `step` блок `C[bi, bj]` содержит сумму всех +произведений блоков строки `bi` матрицы `A` и столбца `bj` матрицы `B`. ## 4. Детали реализации Файлы реализации: `seq/include/ops_seq.hpp`, `seq/src/ops_seq.cpp`. -`ValidationImpl` проверяет размерность входа. -`PreProcessingImpl` очищает выходной буфер. -`RunImpl` выполняет всё вычисление, включая padding, блочное умножение и копирование ответа. -`PostProcessingImpl` не задействован. +`ValidationImpl` проверяет размерность входа. `PreProcessingImpl` очищает выходной буфер. `RunImpl` выполняет +всё вычисление, включая padding, блочное умножение и копирование ответа. `PostProcessingImpl` не задействован. Ключевой фрагмент: @@ -49,11 +51,14 @@ for (std::size_t bi = 0; bi < q; ++bi) { } ``` -Функция `MulAddBlock` умножает один блок `A[bi, bk]` на один блок `B[bk, bj]` и добавляет результат в блок `C[bi, bj]`. В последовательной версии гонок быть не может, так как все блоки обрабатываются одним потоком. +Функция `MulAddBlock` умножает один блок `A[bi, bk]` на один блок `B[bk, bj]` и добавляет результат в блок +`C[bi, bj]`. В последовательной версии гонок быть не может, так как все блоки обрабатываются одним потоком. ## 5. Проверка корректности -Функциональные тесты сравнивают результат с независимым обычным умножением матриц. Используются размеры `1`, `2`, `3`, `4`, `7`, `10`, `15`, что покрывает минимальный случай и размеры, где padding действительно влияет на внутреннее представление. +Функциональные тесты сравнивают результат с независимым обычным умножением матриц. Используются размеры `1`, +`2`, `3`, `4`, `7`, `10`, `15`, что покрывает минимальный случай и размеры, где padding действительно влияет +на внутреннее представление. Примеры характерных проверок: @@ -81,15 +86,16 @@ mpirun --oversubscribe -n 1 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkh ## 7. Результаты - | size | workers | mode | median time, s | comment | | ---- | ------- | -------- | -------------- | ---------------------------- | | 640 | 1 | task | 2.4367350032 | baseline для ускорения | | 640 | 1 | pipeline | 2.4557342674 | baseline для pipeline-режима | - -Значения получены локальным запуском `ppc_perf_tests` с фильтром `*cheremkhin_a_matr_mult_cannon_alg_seq*`. Именно эти значения используются как знаменатель при вычислении ускорения остальных backend-ов. +Значения получены локальным запуском `ppc_perf_tests` с фильтром `*cheremkhin_a_matr_mult_cannon_alg_seq*`. +Именно эти значения используются как знаменатель при вычислении ускорения остальных backend-ов. ## 8. Выводы -SEQ-версия задаёт корректный baseline и фиксирует общую блочную схему алгоритма. Она не даёт ускорения, но позволяет честно сравнивать все параллельные реализации с одним и тем же способом подготовки данных и одним критерием корректности. \ No newline at end of file +SEQ-версия задаёт корректный baseline и фиксирует общую блочную схему алгоритма. Она не даёт ускорения, но +позволяет честно сравнивать все параллельные реализации с одним и тем же способом подготовки данных и одним +критерием корректности. diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/stl/report.md b/tasks/cheremkhin_a_matr_mult_cannon_alg/stl/report.md index 177023369a..743dc51c56 100644 --- a/tasks/cheremkhin_a_matr_mult_cannon_alg/stl/report.md +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/stl/report.md @@ -6,13 +6,18 @@ ## 1. Контекст -STL-версия нужна для проверки того, насколько хорошо блочный алгоритм Кэннона переносится на ручное управление асинхронными задачами стандартной библиотеки C++. В отличие от OpenMP и TBB, здесь нет внешнего планировщика циклов: код сам выбирает число worker-задач, делит диапазон на части и ожидает завершения каждой части. +STL-версия нужна для проверки того, насколько хорошо блочный алгоритм Кэннона переносится на ручное управление +асинхронными задачами стандартной библиотеки C++. В отличие от OpenMP и TBB, здесь нет внешнего планировщика +циклов: код сам выбирает число worker-задач, делит диапазон на части и ожидает завершения каждой части. -В реализации используется `std::async` с политикой `std::launch::async` и набор `std::future`. Это не скрытая OpenMP/TBB-версия, а backend на средствах стандартной библиотеки: запуск задач, хранение futures и ожидание результата описаны явно в `stl/src/ops_stl.cpp`. +В реализации используется `std::async` с политикой `std::launch::async` и набор `std::future`. Это не +скрытая OpenMP/TBB-версия, а backend на средствах стандартной библиотеки: запуск задач, хранение futures и +ожидание результата описаны явно в `stl/src/ops_stl.cpp`. ## 2. Постановка задачи -На вход подаются размер `n` и две квадратные матрицы `A` и `B`, сохранённые в одномерных векторах в построчном порядке. Требуется вычислить матрицу `C = A * B` и вернуть её в таком же формате. +На вход подаются размер `n` и две квадратные матрицы `A` и `B`, сохранённые в одномерных векторах в построчном +порядке. Требуется вычислить матрицу `C = A * B` и вернуть её в таком же формате. Вход считается корректным, если: @@ -20,7 +25,9 @@ STL-версия нужна для проверки того, насколько - размер `A` равен `n * n`; - размер `B` равен `n * n`. -Последовательная версия `seq` используется как baseline по корректности и времени. Для размеров, которые неудобно делятся на блоки, входные матрицы дополняются нулями до размера `np x np`; после вычисления в выходной вектор копируется только исходная область `n x n`. +Последовательная версия `seq` используется как baseline по корректности и времени. Для размеров, которые +неудобно делятся на блоки, входные матрицы дополняются нулями до размера `np x np`; после вычисления в +выходной вектор копируется только исходная область `n x n`. ## 3. Базовый алгоритм @@ -32,11 +39,15 @@ STL-версия нужна для проверки того, насколько 4. Для каждого блока результата `C[bi, bj]` выполняется накопление произведений блоков `A[bi, bk] * B[bk, bj]`. 5. Из padded-матрицы результата копируется исходная область `n x n`. -Временная сложность вычислительного ядра остаётся `O(n^3)`. Дополнительная память — `O(np^2)` для padded-матриц `a`, `b` и `c`. Корректность блочной схемы основана на том, что каждый блок результата после цикла по `step` содержит сумму всех нужных блочных произведений. +Временная сложность вычислительного ядра остаётся `O(n^3)`. Дополнительная память — `O(np^2)` для +padded-матриц `a`, `b` и `c`. Корректность блочной схемы основана на том, что каждый блок результата после +цикла по `step` содержит сумму всех нужных блочных произведений. ## 4. Схема распараллеливания -Повторяющаяся схема "параллельный цикл по независимым индексам" вынесена во вспомогательную функцию `ParallelFor`. Она принимает количество итераций `count`, запрошенное число worker-ов и функцию обработки одного индекса. +Повторяющаяся схема "параллельный цикл по независимым индексам" вынесена во вспомогательную функцию +`ParallelFor`. Она принимает количество итераций `count`, запрошенное число worker-ов и функцию обработки +одного индекса. Число worker-ов выбирается так: @@ -44,9 +55,11 @@ STL-версия нужна для проверки того, насколько const std::size_t workers = std::max(1, std::min(count, requested_threads)); ``` -Это защищает от запуска большего числа задач, чем есть независимых итераций. Если worker только один, функция выполняет обычный последовательный цикл без создания `std::async`. +Это защищает от запуска большего числа задач, чем есть независимых итераций. Если worker только один, функция +выполняет обычный последовательный цикл без создания `std::async`. -Для нескольких worker-ов диапазон делится на чанки размера `ceil(count / workers)`. Каждая асинхронная задача получает полуинтервал `[begin, end)`: +Для нескольких worker-ов диапазон делится на чанки размера `ceil(count / workers)`. Каждая асинхронная задача +получает полуинтервал `[begin, end)`: ```cpp const std::size_t chunk = CeilDiv(count, workers); @@ -76,13 +89,16 @@ for (auto &task : tasks) { } ``` -Это важный момент для STL-версии: если ожидать каждую задачу сразу после создания, работа фактически сериализуется. В текущей реализации сначала создаются все асинхронные задачи, а затем вызывается `get()`, поэтому независимые чанки могут выполняться параллельно. +Это важный момент для STL-версии: если ожидать каждую задачу сразу после создания, работа фактически +сериализуется. В текущей реализации сначала создаются все асинхронные задачи, а затем вызывается `get()`, +поэтому независимые чанки могут выполняться параллельно. ## 5. Детали реализации Файлы реализации: `stl/include/ops_stl.hpp`, `stl/src/ops_stl.cpp`. -`ValidationImpl` проверяет размерность входных матриц. `PreProcessingImpl` очищает выходной буфер. Основная работа находится в `RunImpl`; `PostProcessingImpl` дополнительных действий не выполняет. +`ValidationImpl` проверяет размерность входных матриц. `PreProcessingImpl` очищает выходной буфер. Основная +работа находится в `RunImpl`; `PostProcessingImpl` дополнительных действий не выполняет. `ParallelFor` используется в трёх местах: @@ -104,13 +120,18 @@ ParallelFor(q * q, threads, [&](std::size_t block_idx) { }); ``` -Каждая итерация `block_idx` соответствует одному блоку `C[bi, bj]`. Поэтому две разные worker-задачи не записывают в один и тот же блок результата. Матрицы `a` и `b` после фазы копирования только читаются. Благодаря этому в реализации не нужны `mutex`, `atomic` или другие примитивы защиты записи. +Каждая итерация `block_idx` соответствует одному блоку `C[bi, bj]`. Поэтому две разные worker-задачи не +записывают в один и тот же блок результата. Матрицы `a` и `b` после фазы копирования только читаются. +Благодаря этому в реализации не нужны `mutex`, `atomic` или другие примитивы защиты записи. ## 6. Проверка корректности -Корректность проверяется общими функциональными тестами проекта. Для каждого теста ожидаемый результат считается независимым тройным циклом обычного умножения матриц, а затем сравнивается с результатом STL-версии с допуском `1e-7`. +Корректность проверяется общими функциональными тестами проекта. Для каждого теста ожидаемый результат +считается независимым тройным циклом обычного умножения матриц, а затем сравнивается с результатом STL-версии +с допуском `1e-7`. -Покрытые размеры: `1`, `2`, `3`, `4`, `7`, `10`, `15`. Они проверяют минимальный случай, малые матрицы и размеры, при которых padding влияет на внутреннее представление. +Покрытые размеры: `1`, `2`, `3`, `4`, `7`, `10`, `15`. Они проверяют минимальный случай, малые матрицы и +размеры, при которых padding влияет на внутреннее представление. Команда запуска функциональных тестов: @@ -119,7 +140,8 @@ export PPC_NUM_PROC=1 scripts/run_tests.py --running-type=threads --counts 1 2 4 ``` -Для производительности используется общий тест на матрицах `640 x 640`; он запускает оба режима performance-инфраструктуры: `task` и `pipeline`. +Для производительности используется общий тест на матрицах `640 x 640`; он запускает оба режима +performance-инфраструктуры: `task` и `pipeline`. ## 7. Экспериментальная среда @@ -144,7 +166,6 @@ mpirun --oversubscribe -n 4 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkh ## 8. Результаты - | size | workers | mode | median time, s | speedup vs seq | efficiency | | ---- | ------- | -------- | -------------- | -------------- | ---------- | | 640 | 1 | task | 0.8761261350 | 2.7813 | 2.7813 | @@ -154,11 +175,17 @@ mpirun --oversubscribe -n 4 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkh | 640 | 2 | pipeline | 0.8812324666 | 2.7867 | 1.3934 | | 640 | 4 | pipeline | 0.9960843862 | 2.4654 | 0.6163 | - -Ускорение рассчитано как `T_seq / T_stl`, а эффективность — как `speedup / workers`. Использованный SEQ baseline: `2.4367350032` для `task` и `2.4557342674` для `pipeline`. Для STL-версии отдельно важно интерпретировать цену создания асинхронных задач и ожидания `future::get()`: на малых или плохо гранулированных диапазонах эти накладные расходы могут съесть выигрыш от параллельного вычисления блоков. +Ускорение рассчитано как `T_seq / T_stl`, а эффективность — как `speedup / workers`. Использованный SEQ +baseline: `2.4367350032` для `task` и `2.4557342674` для `pipeline`. Для STL-версии отдельно важно +интерпретировать цену создания асинхронных задач и ожидания `future::get()`: на малых или плохо +гранулированных диапазонах эти накладные расходы могут съесть выигрыш от параллельного вычисления блоков. ## 9. Выводы -STL-реализация сохраняет общую блочную схему алгоритма Кэннона, но вручную управляет разбиением работы. Её сильная сторона — прозрачная карта "worker-задача → диапазон индексов"; слабая сторона — отсутствие специализированного runtime-планировщика и возможные накладные расходы `std::async`. +STL-реализация сохраняет общую блочную схему алгоритма Кэннона, но вручную управляет разбиением работы. Её +сильная сторона — прозрачная карта "worker-задача → диапазон индексов"; слабая сторона — отсутствие +специализированного runtime-планировщика и возможные накладные расходы `std::async`. -С точки зрения корректности версия безопасна: запись разделена по строкам или по независимым блокам результата, а чтение общих padded-матриц начинается только после завершения фазы их заполнения. Поэтому дополнительные блокировки в текущей схеме не требуются. \ No newline at end of file +С точки зрения корректности версия безопасна: запись разделена по строкам или по независимым блокам +результата, а чтение общих padded-матриц начинается только после завершения фазы их заполнения. Поэтому +дополнительные блокировки в текущей схеме не требуются. diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/tbb/report.md b/tasks/cheremkhin_a_matr_mult_cannon_alg/tbb/report.md index e972d540e0..66eee9dac1 100644 --- a/tasks/cheremkhin_a_matr_mult_cannon_alg/tbb/report.md +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/tbb/report.md @@ -6,15 +6,19 @@ ## 1. Контекст -oneTBB-версия использует ту же блочную декомпозицию, что и SEQ/OMP, но планирование независимых работ передаётся runtime-библиотеке TBB. Это позволяет описать работу как набор диапазонов, а не вручную управлять потоками. +oneTBB-версия использует ту же блочную декомпозицию, что и SEQ/OMP, но планирование независимых работ +передаётся runtime-библиотеке TBB. Это позволяет описать работу как набор диапазонов, а не вручную управлять +потоками. ## 2. Постановка задачи -Требуется вычислить произведение двух квадратных матриц `C = A * B`. Вход и выход совпадают с общей постановкой: `n`, вектор `A`, вектор `B`, результат в построчном одномерном виде. +Требуется вычислить произведение двух квадратных матриц `C = A * B`. Вход и выход совпадают с общей +постановкой: `n`, вектор `A`, вектор `B`, результат в построчном одномерном виде. ## 3. Базовый алгоритм -Алгоритм выбирает `q`, `bs` и `np`, копирует данные в padded-буферы, вычисляет блоки результата и копирует ответ обратно. Независимость блоков `C[bi, bj]` позволяет использовать двумерный диапазон TBB. +Алгоритм выбирает `q`, `bs` и `np`, копирует данные в padded-буферы, вычисляет блоки результата и копирует +ответ обратно. Независимость блоков `C[bi, bj]` позволяет использовать двумерный диапазон TBB. ## 4. Схема распараллеливания @@ -24,9 +28,11 @@ oneTBB-версия использует ту же блочную декомпо oneapi::tbb::global_control control(oneapi::tbb::global_control::max_allowed_parallelism, requested_threads); ``` -Здесь `requested_threads` берётся из `PPC_NUM_THREADS`. Это ограничивает максимальный параллелизм TBB runtime на время выполнения `RunImpl`. +Здесь `requested_threads` берётся из `PPC_NUM_THREADS`. Это ограничивает максимальный параллелизм TBB runtime +на время выполнения `RunImpl`. -Для копирования входа и выхода используется `oneapi::tbb::parallel_for` по строкам. Основное вычисление использует `oneapi::tbb::blocked_range2d(0, q64, 0, q64)`: +Для копирования входа и выхода используется `oneapi::tbb::parallel_for` по строкам. Основное вычисление +использует `oneapi::tbb::blocked_range2d(0, q64, 0, q64)`: ```cpp oneapi::tbb::parallel_for(oneapi::tbb::blocked_range2d(0, q64, 0, q64), @@ -42,17 +48,21 @@ oneapi::tbb::parallel_for(oneapi::tbb::blocked_range2d(0, q64, 0, }); ``` -Grain size явно не задан, поэтому используется разбиение по умолчанию. Это упрощает код, но оставляет runtime-у право выбирать размер подзадач. Для очень малых `q` накладные расходы TBB могут быть заметны. +Grain size явно не задан, поэтому используется разбиение по умолчанию. Это упрощает код, но оставляет +runtime-у право выбирать размер подзадач. Для очень малых `q` накладные расходы TBB могут быть заметны. ## 5. Детали реализации Файлы реализации: `tbb/include/ops_tbb.hpp`, `tbb/src/ops_tbb.cpp`. -Локальных аккумуляторов не требуется: каждый TBB-task записывает в свой блок `C[bi, bj]`. Общие входные матрицы `a` и `b` только читаются после фазы копирования. Запись в `out` также разделена по строкам, поэтому гонки отсутствуют. +Локальных аккумуляторов не требуется: каждый TBB-task записывает в свой блок `C[bi, bj]`. Общие входные +матрицы `a` и `b` только читаются после фазы копирования. Запись в `out` также разделена по строкам, поэтому +гонки отсутствуют. ## 6. Проверка корректности -TBB-версия проверяется теми же функциональными тестами, что и остальные реализации. Результат сравнивается с независимым последовательным oracle с допуском `1e-7`. +TBB-версия проверяется теми же функциональными тестами, что и остальные реализации. Результат сравнивается с +независимым последовательным oracle с допуском `1e-7`. Команда для потоковых тестов: @@ -78,7 +88,7 @@ mpirun --oversubscribe -n 4 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkh ## 8. Результаты | size | workers | mode | median time, s | speedup vs seq | efficiency | -|---:|---:|---|---:|---:|---:| +| ---: | ---: | --- | ---: | ---: | ---: | | 640 | 1 | task | 0.8220091702 | 2.9644 | 2.9644 | | 640 | 2 | task | 0.9062373528 | 2.6888 | 1.3444 | | 640 | 4 | task | 0.9534718392 | 2.5556 | 0.6389 | @@ -86,8 +96,12 @@ mpirun --oversubscribe -n 4 ./build/bin/ppc_perf_tests --gtest_filter='*cheremkh | 640 | 2 | pipeline | 0.8468019650 | 2.9000 | 1.4500 | | 640 | 4 | pipeline | 0.9090972242 | 2.7013 | 0.6753 | -Ускорение рассчитано относительно SEQ baseline: `2.4367350032` для `task` и `2.4557342674` для `pipeline`. По этим данным TBB быстрее последовательной версии, но эффективность снижается при росте числа workers из-за накладных расходов runtime и ограниченной гранулярности блочного разбиения. +Ускорение рассчитано относительно SEQ baseline: `2.4367350032` для `task` и `2.4557342674` для `pipeline`. По +этим данным TBB быстрее последовательной версии, но эффективность снижается при росте числа workers из-за +накладных расходов runtime и ограниченной гранулярности блочного разбиения. ## 9. Выводы -TBB-реализация хорошо соответствует блочной природе алгоритма: основная работа описывается двумерным диапазоном блоков результата. Главные факторы эффективности — размер `q`, стоимость одного блока и накладные расходы runtime на разбиение диапазона. +TBB-реализация хорошо соответствует блочной природе алгоритма: основная работа описывается двумерным +диапазоном блоков результата. Главные факторы эффективности — размер `q`, стоимость одного блока и накладные +расходы runtime на разбиение диапазона. From b64a1837a489783aab4484f825993e1701876544 Mon Sep 17 00:00:00 2001 From: andry300000 Date: Tue, 26 May 2026 12:57:35 +0000 Subject: [PATCH 3/4] fix --- tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json b/tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json index 0be0208fc6..3f9fcd7950 100644 --- a/tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json @@ -3,7 +3,7 @@ "all": "enabled", "omp": "enabled", "seq": "enabled", - "stl": "enabled", + "stl": "disabled", "tbb": "enabled" }, "tasks_type": "threads" From eb7432004817560d168a9b2f30010436071a15a6 Mon Sep 17 00:00:00 2001 From: andry300000 Date: Thu, 4 Jun 2026 18:13:29 +0000 Subject: [PATCH 4/4] stl work enabled --- tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json b/tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json index 3f9fcd7950..0be0208fc6 100644 --- a/tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json +++ b/tasks/cheremkhin_a_matr_mult_cannon_alg/settings.json @@ -3,7 +3,7 @@ "all": "enabled", "omp": "enabled", "seq": "enabled", - "stl": "disabled", + "stl": "enabled", "tbb": "enabled" }, "tasks_type": "threads"