116 lines
6.7 KiB
Markdown
116 lines
6.7 KiB
Markdown
# Лабораторная работа 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). Здесь их всего две: замеры в
|
||
отладчике и замеры без повторов.
|