# Лабораторная работа 1. Последовательное вычисление и профилирование **Время на работу:** 2 пары. ## Цель работы Создать базовую линию: последовательную реализацию своей задачи и её замеры. Без неё невозможно показать ускорение в следующих лабораторных — нечего будет ускорять. ## Теория ### Последовательное вычисление Программа выполняет операцию за операцией, в одном потоке управления. Это простейшая и самая предсказуемая модель: результат не зависит от порядка выполнения, время выполнения = сумма времён всех операций. Все дальнейшие лабораторные сравниваются с этой базой. ### Два типа нагрузки **CPU-bound** — программа ограничена скоростью процессора: считает, шифрует, сортирует. Время определяется числом элементарных операций. **I/O-bound** — программа ограничена ожиданием: сеть, диск, база данных. Время определяется временем ожидания ответов, процессор при этом простаивает. Отличить просто: если удалить «полезную работу», а программа всё равно медленная — это I/O-bound; если быстрая — CPU-bound. Тип нагрузки определяет, какая модель параллелизма поможет (потоки — для I/O, процессы — для CPU, async — для I/O в больших количествах). ### Время: настенное и процессорное - `time.perf_counter()` — «настенные» часы: сколько времени прошло реально (включая сон и ожидание). Ими меряют всё в этом курсе. - `time.process_time()` — только время, потраченное процессором. Разница между ними для I/O-задачи огромна — это её диагноз. Обе функции монотонные (не откатываются назад, как `time.time()` при переводе часов), имеют наносекундное разрешение — всегда используйте их, а не `time.time()`. ### Профилирование Профилировщик отвечает на вопрос «где программа проводит время». `cProfile` собирает статистику по функциям: число вызовов, суммарное время, время без вложенных вызовов. ```python import cProfile cProfile.run("main()", sort="cumulative") ``` Интерпретация: колонка `cumtime` — общее время функции (с вызовами), `totime` — собственное. Первые строки обычно сразу показывают узкое место. ## Задание Ваша задача и параметры — по варианту (`common/варианты.md`), доступ: ```python from common.tasks import get_variant, TASKS v = get_variant(13) # ваш номер task = TASKS[v["task_name"]] data = task["build"](v["cpu_params"]) ``` Заполните `solution.py` (интерфейсы менять нельзя): 1. **`run_cpu_sequential(task_name, params, parts=4)`** — выполняет задачу последовательно: `build` → разбиение на `parts` кусков → `kernel` для каждого куска в цикле → `combine` → вернуть `checksum`. Куски обязательны: в следующих лабораторных те же куски уйдут воркерам. 2. **`run_io_sequential(items, delay)`** — для каждого элемента вызывает `common.tasks.io_fetch(item, delay)` и собирает результаты в список (в порядке возрастания item). 3. **`main()`** — запускает обе части на параметрах своего варианта, выводит таблицу времен (`common.benchmark.make_table`) и значения checksum. ### Измерения для отчёта - CPU-часть: T(1) при разбиении на 1, 2, 4 куска (убедитесь, что checksum одинаков и время почти не меняется — разбиение не должно менять работу). - I/O-часть: суммарное время для items из варианта; сравните `perf_counter` и `process_time` — увидите, сколько времени машина спит. - Профилирование CPU-части: `cProfile.run(...)`, топ-5 функций по времени. Укажите в отчёте, какая функция — узкое место. ## Контрольные вопросы 1. Чем CPU-bound задача отличается от I/O-bound? Какая у вас? 2. Почему для замеров нужен `perf_counter`, а не `time.time()`? 3. Что показывает `cumtime` и `tototime` в выводе cProfile? 4. Почему checksum не должен зависеть от числа кусков? Что это проверяет? ## Что сдаётся `solution.py` + отчёт по шаблону: таблицы T(1/2/4 кусков), время I/O-части, сравнение perf_counter/process_time, топ-5 из cProfile, анализ. ## Критерии оценки | Пункт | Баллы | |---|---| | `run_cpu_sequential` работает, checksum совпадает при разном разбиении | 2 | | `run_io_sequential` работает | 1 | | Замеры и сравнение perf_counter/process_time | 2 | | Профилирование и вывод об узком месте | 2 | | Отчёт: таблицы, анализ, ответы на вопросы | 3 | ## Типичные ошибки См. `common/типичные_ошибки.md` (пп. 8, 9). Здесь их всего две: замеры в отладчике и замеры без повторов.