124 lines
5.4 KiB
Python
124 lines
5.4 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""Лабораторная работа 8: распределённые вычисления (mpi4py).
|
||
|
||
Заполните функции ниже. ИНТЕРФЕЙСЫ МЕНЯТЬ НЕЛЬЗЯ — по ним работают
|
||
автотесты (tests/test_lab08.py; если mpi4py не установлен — тесты
|
||
пропускаются, но работа сдаётся с запуском через mpiexec).
|
||
|
||
Запуск из корня курса:
|
||
python lab08_mpi/solution.py (один ранг — отладка)
|
||
mpiexec -n 4 python lab08_mpi/solution.py (4 ранга — замеры)
|
||
"""
|
||
import os
|
||
import random
|
||
import sys
|
||
import time
|
||
|
||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||
|
||
from common.tasks import TASKS, get_variant
|
||
|
||
try:
|
||
from mpi4py import MPI
|
||
HAVE_MPI = True
|
||
except ImportError:
|
||
HAVE_MPI = False
|
||
|
||
# TODO: впишите свой номер в журнале (1..20)
|
||
VARIANT_NUMBER = 0
|
||
|
||
|
||
def _group_for_ranks(chunks, size):
|
||
"""Распределить куски по рангам round-robin: список из size групп.
|
||
|
||
scatter ожидает список ровно из size элементов, а естественных кусков
|
||
может быть другое число (например, nqueens всегда даёт N кусков) —
|
||
поэтому группируем.
|
||
"""
|
||
# TODO: groups = [[] for _ in range(size)]; groups[i % size].append(ch)
|
||
raise NotImplementedError
|
||
|
||
|
||
def mpi_run(task_name: str, params: dict):
|
||
"""CPU-задача на всех рангах: scatter групп -> kernel -> gather -> combine.
|
||
|
||
Схема:
|
||
ранг 0: build -> split(data, size) -> _group_for_ranks
|
||
scatter раздаёт ГРУППЫ кусков (по одной на ранг)
|
||
каждый ранг: my_partials = [task["kernel"](ch) for ch in my_group]
|
||
gather собирает списки частичных результатов
|
||
ранг 0: развернуть в плоский список -> combine ОДИН раз -> checksum
|
||
|
||
Функции пиккелить не нужно (в отличие от multiprocessing) — только данные.
|
||
|
||
Для задачи 'life': данные — (grid, steps), параллелятся ПОКОЛЕНИЯ.
|
||
Ранг 0 держит текущее поле; каждый шаг: scatter полос -> kernel ->
|
||
gather -> combine -> новое поле. Число шагов раздаётся через bcast.
|
||
"""
|
||
if not HAVE_MPI:
|
||
raise RuntimeError("mpi4py не установлен (см. методичку, раздел Установка)")
|
||
task = TASKS[task_name]
|
||
comm = MPI.COMM_WORLD
|
||
rank = comm.Get_rank()
|
||
size = comm.Get_size()
|
||
|
||
if task_name == "life":
|
||
# TODO: цикл по поколениям (см. схему выше); в конце bcast поля
|
||
# с ранга 0 всем, чтобы checksum был у всех рангов
|
||
raise NotImplementedError
|
||
|
||
# TODO: общий случай по схеме выше; вернуть checksum на ранге 0 (None на других)
|
||
raise NotImplementedError
|
||
|
||
|
||
def mpi_pi(N: int) -> float:
|
||
"""π методом Монте-Карло на всех рангах (reduce).
|
||
|
||
Каждый ранг бросает N//size точек с зерном random.Random(100 + rank).
|
||
reduce(MPI.SUM) сворачивает попадания на ранге 0.
|
||
Вернуть π (на ранге 0 — вычисленное значение, на остальных 0.0).
|
||
"""
|
||
# TODO: my_n = N // size; цикл бросков; comm.reduce(inside, op=MPI.SUM, root=0)
|
||
raise NotImplementedError
|
||
|
||
|
||
def main():
|
||
if not HAVE_MPI:
|
||
print("mpi4py не установлен. Инструкция — в методичке, раздел «Установка».")
|
||
print("Кратко (Windows): MS-MPI runtime + pip install mpi4py")
|
||
sys.exit(1)
|
||
|
||
v = get_variant(VARIANT_NUMBER)
|
||
comm = MPI.COMM_WORLD
|
||
rank = comm.Get_rank()
|
||
size = comm.Get_size()
|
||
|
||
if rank == 0:
|
||
print(f"Вариант {v['variant']}: {v['describe']}")
|
||
print(f"Запущено рангов: {size}\n")
|
||
|
||
# --- pi через reduce ------------------------------------------------------
|
||
pi = mpi_pi(1_000_000)
|
||
if rank == 0:
|
||
print(f"π ≈ {pi:.6f} (Монте-Карло, 1 000 000 бросков суммарно)")
|
||
|
||
# --- основная задача --------------------------------------------------------
|
||
comm.barrier()
|
||
t0 = time.perf_counter()
|
||
checksum = mpi_run(v["task_name"], v["cpu_params"])
|
||
dt = time.perf_counter() - t0
|
||
|
||
if rank == 0:
|
||
from common.tasks import run_sequential
|
||
ref, _ = run_sequential(v["task_name"], v["cpu_params"], parts=1)
|
||
ok = checksum == ref or (isinstance(checksum, float) and
|
||
abs(checksum - ref) < 1e-6 * max(1, abs(ref)))
|
||
print(f"задача: {dt:.3f} c, checksum совпадает с последовательным: {ok}")
|
||
print("\nЗапустите при разном числе рангов для таблицы T(p):")
|
||
print(" mpiexec -n 1 python lab08_mpi/solution.py")
|
||
print(" mpiexec -n 2 python lab08_mpi/solution.py")
|
||
print(" mpiexec -n 4 python lab08_mpi/solution.py")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main() |