Files
Sprinter-SDCC/applications/PoP/docs/perf_registry.md
T
snark13 fd570c7eb8 Замер сцены 11/15 и единый реестр оптимизаций
Новая целевая сцена: уровень 11 комната 15 — два факела, чомпер, страж.
В отличие от 13/23 (разовый пик на каскаде плит) здесь дорога САМА
статика: Кид и страж стоят, а кадр стоит 801 768 тактов = 1,86
растрового кадра, период 4 растра во всех 866 интервалах прогона.

Фазы: синяя 293 238 (heal 141 048 + логика 152 190), зелёная 320 916
(loose_tick 28 872 + process_trobs 92 448 + redraw_needed 190 260),
циан 187 758.  Впечатление «циан ~150 % кадра» не подтвердилось: за 867
кадров разброс циана 174 такта, это 0,44 растра.

Главная находка — 190 260 тактов на ОДИН тайл (pop_dbg_rdmax_tot = 1).
Пламя факела запекается в ячейке правого соседа, то есть поверх чомпера,
и process_trobs метит соседа (порт set_redraw_anim_right).  Оригинал на
такую пометку рисует ТОЛЬКО слой anim, мы же отвечаем heal 32x64 плюс
полный draw_tile — со всеми слоями, которых пламя не касалось.

Заодно разложена цена одного блита фона (брейкпоинты на резидентных
адресах внутри pop_blit_b, temp0 на входе, 1603 блита): фиксированная
накладная 6 126 тактов на ЛЮБОЙ блит — пролог с IX-фреймом 810,
atlas_image 672, w0_map с чтением шапки 2 400, cd_touch 2 069, unmap 175.
У самого дешёвого блита это 59 % цены, у пламени 16x18 пиксели тянут
лишь 12 %.  Причины ровно те, на которые указал пользователь:
16-битные аргументы там, где хватает 8 бит, и адресация через IX.

perf_registry.md сводит в один отсортированный список всё отложенное из
perf_green_phase (G1-G9), perf_cyan_phase (C1-C7), perf_backlog (1-7),
HEAL-WIDTH и сегодняшние находки — с пометкой замер/модель/гипотеза.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 10:21:02 +03:00

217 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Реестр оптимизаций: всё отложенное, в одном списке
Собрано 2026-08-19 из [`perf_green_phase.md`](perf_green_phase.md) (G1-G9),
[`perf_cyan_phase.md`](perf_cyan_phase.md) (C1-C7),
[`perf_backlog.md`](perf_backlog.md) (позиции 1-7),
[`../roomtest/TASKS_OPEN.md`](../roomtest/TASKS_OPEN.md) (HEAL-WIDTH) и из
свежего разбора сцены [`perf_l11_room15.md`](perf_l11_room15.md).
**База для процентов — работа кадра в 11/15: 801 768 тактов** (замер
`09f32ce`). Где эффект относится к другой сцене, это сказано явно.
Оценки помечены: **[замер]** — измерено; **[модель]** — посчитано по
измеренным составляющим; **[гипотеза]** — не мерено, нужен прогон.
---
## 1. Цена одного блита фона — разложена [замер 2026-08-19]
Метод: брейкпоинты на резидентных адресах внутри `pop_blit_b` (0x5C50),
`temp0` на входе, разница `totalcycles` на каждом вызове. 1603 блита.
| этап | такты | постоянство |
|---|---:|---|
| пролог + аргументы + грубый отсев | **810** | ровно, всегда |
| `atlas_image` | **672** | ровно, всегда |
| `gfx_w0_map` + чтение шапки ленты + арифметика клипа | **2 400** | ровно, всегда |
| ядро блита (пиксели) | 4 380 … 26 592 | по размеру кадра |
| `pop_cd_touch` | **2 069** (пакетный путь) / 4 115 (настоящая пометка) | почти ровно |
| `gfx_w0_unmap` + эпилог | **175** | ровно, всегда |
| **весь блит** | 10 458 … 32 718, медиана **16 674** | |
**Фиксированная накладная = 6 126 тактов на любой блит, хоть 8×8.**
У самого дешёвого блита (10 458) это **59 % цены**; у пламени факела 16×18
пиксели тянут ~1 700 из ~14 000, то есть **12 %**.
Это и есть ответ на вопрос «почему маленький блит стоит 14 000». Причины
ровно те, о которых спрашивал пользователь:
- **810 на пролог** — `call ___sdcc_enter_ix`, IX-фрейм и шесть чтений
`N(ix)`: третий и четвёртый аргументы (`int x`, `int ybottom`) идут
СТЕКОМ, каждое обращение 19 тактов Z80;
- **2 069 на `pop_cd_touch`** даже по пакетному пути, где вся работа — четыре
сравнения. Сигнатура `(int x, int y, int w, int h)` = 8 байт аргументов,
два из них через стек; `w`/`h` никогда не больше 64, `y` не больше 255,
то есть три из четырёх могли быть `uint8_t`;
- **2 400 на map + шапку** — `gfx_w0_map` (1 086) + `unmap` (264, платится в
конце) + ~1 000 на чтение четырёх байт заголовка и арифметику;
- **672 на `atlas_image`** — маппинг W3, чтение записи каталога, возврат W3;
размеры при этом читаются и выбрасываются (см. §3, C6).
**Блитов за кадр в 11/15: 8** [замер] — все в зелёной фазе (6 на `draw_tile`
чомпера, 2 на пламя факелов). Синяя и циан через `pop_blit_b` не ходят
вовсе: heal и персонажи идут своими путями. Значит фиксированные накладные
блита стоят сцене **8 × 6 126 = 49 000 тактов/кадр (6,1 % работы)**.
---
## 2. Модель зелёной фазы 11/15 [модель, сходится с 320 916 замера]
| статья | такты | доля фазы |
|---|---:|---:|
| 8 блитов фона (из них 6 126×8 = 49 000 накладных) | 133 400 | 41 % |
| диспетчер `draw_tile` (один тайл чомпера) | 56 200 | 18 % |
| цикл `pop_process_trobs` без блитов пламени | 59 100 | 18 % |
| `pop_loose_tick` (плит в комнате НЕТ) | 28 872 | 9 % |
| heal 32×64 в `pop_chomp_redraw` | 34 000 | 11 % |
| шов / ворота соседа | 9 336 | 3 % |
Больше половины фазы — не пиксели, а обвязка вокруг них.
---
## 3. Список приёмов, отсортированный по эффекту
### P1. Чомпер под факелом — возвращать только слой anim [модель: −160 000, 20 % работы]
Разбор в [`perf_l11_room15.md`](perf_l11_room15.md) §3. Сейчас пометка от
факела обрабатывается как `heal 32×64 + полный draw_tile` (190 260 тактов на
единственный перерисованный тайл кадра); оригинал в этом случае рисует
ТОЛЬКО `draw_tile_anim` — графику чомпера поверх свежего пламени.
Останется 1-2 блита челюстей ≈ 20 000-33 000. **Риск низкий**: это
сближение с оригиналом, а не отход от него. Побочно снимает широкую пометку
«фон трогали» вокруг тайла чомпера — см. P3.
Новое, найдено 2026-08-19.
### P2. Логика двух Char — 152 190 тактов БЕЗ единого блита [гипотеза: −40 000 … 70 000]
`m1→C`: `kid_tick`, `pop_phys_tick`, `pop_guard_tick`, `pop_guard_phys_tick`,
боёвка, `pop_check_can_guard_see_kid`. Графики там нет вообще, а стоит это
19 % работы кадра — столько же, сколько вся отрисовка обоих персонажей.
**Сначала замер** (зонды внутрь синей фазы, нужна пересборка), потом решение.
Кандидаты по прошлым находкам: `sdcc_z80_stack_locals_hot_loop` (локали в
IX-фрейме), деления в луче видимости, банковые трамплины на каждый шаг.
Новое, найдено 2026-08-19.
### P3. Персонажи будятся каждый кадр [модель: −70 000, следствие P1]
heal 141 048 + отрисовка 148 566 = 290 000 тактов (36 % работы) уходят на то,
что `pop_char_skip_mask` не может пропустить ни Кида, ни стража: фон трогают
каждый кадр.
- **Кида** спасает P1: широкая пометка вокруг чомпера исчезнет;
- **стража спасти нельзя** — пламя правого факела (0,7) рисуется в ячейке
(0,8), где он и стоит. Там фон честно меняется, и оригинал персонажа тоже
перерисовывает.
### P4. Накладные блита — четыре независимые правки [модель: −28 000 в 11/15; больше в сценах с 20+ блитами]
| правка | на блит | источник |
|---|---:|---|
| `pop_cd_touch`: `uint8_t` вместо `int` для `y`/`w`/`h`, ранний выход пакетного пути | ~−1 300 | новое |
| один `gfx_w0_map`/`unmap` на ГРУППУ блитов | ~1 350 | C5 / backlog §3 |
| размеры ленты из каталога, без `atlas_image` и чтения шапки | ~670 | C6 / backlog §2 |
| `pop_blit_b`: аргументы в 8 бит, где хватает | ~−400 | новое |
Все четыре — низкий риск, механическая работа. Вместе снимают ~3 700 из
6 126 фиксированных.
### P5. `pop_loose_tick` при пустой комнате — 28 872 [замер: 28 000]
В комнате 15 нет ни одной loose-плиты, а фаза всё равно платит 9 % зелёной.
Записано в `perf_backlog.md` §7 как 27 438 — цифра подтвердилась в другой
сцене, значит это постоянная плата, а не особенность 13/23.
Нужен ранний выход по «в комнате нет плит и нет летящих кусков».
### P6. Цикл `pop_process_trobs` — 59 100 без блитов пламени [гипотеза: −20 000 … 40 000]
Префетч кодов тайлов делает `pop_level_access_begin/end` (маппинг окна 0)
каждый кадр; дальше switch по всем trob'ам комнаты. Надо померить, сколько
там trob'ов и куда уходит время.
Новое, найдено 2026-08-19.
### P7. G5. Раскол `draw_tile` на узкие части [оценка дока: −50 000 … 60 000]
Диспетчер + контекст оплачиваются целиком всегда; у оригинала это девять
независимых функций. В 11/15 это те самые 56 200 на один тайл — но если
сделан P1, `draw_tile` чомпера вообще не вызывается, и здесь эффект пропадёт.
Ценность приёма — в ДРУГИХ сценах (13/23, любая комната с плитами).
**Риск средний**: в `draw_tile` собрано много инвариантов (BUG-LOOSE-3,
BUG-LATTICE-DOORTOP, BUG-SEAM-WEDGE-1) — только отдельным заходом с прогоном
всех уровней.
### P8. HEAL-WIDTH — ширина heal'ов по фактическому следу [оценка: 5-6 % цены heal'ов]
ОБЯЗАТЕЛЬНАЯ по решению пользователя (2026-08-18). След плиты 58 px в
подземелье / 57 во дворце против используемых 60 и 64.
Постановка — `TASKS_OPEN.md`, якорь `heal-width`.
### P9. G8 — пометку СОСЕДА ставить узкой полосой (28 px), а не тайлом [гипотеза]
Парная к HEAL-WIDTH. Относится к сценам с падающими плитами (13/23), в 11/15
не играет. Разбор — `perf_green_phase.md` §G8, там же три условия, из-за
которых это не «просто уменьшить число».
### P10. Футпринт персонажа — из физики, а не считать заново [замер: −11 574 на fore-проход]
`backlog` §1. С двумя персонажами — ~23 000 за кадр. Мешает то, что физика
(банк 3) держит `char_col_left/right` в статиках, а слой фона — банк 2.
**Риск средний**: окно fore-клипа заводилось под клинок и брызги.
### P11. Мелочи с известной ценой [замер, `backlog` §7]
| что | цена | где |
|---|---:|---|
| `pop_clip_char_top` — трамплин банк 4 → банк 3 ради одной проверки | 8 892 | `pop_cdraw.c` |
| `cd_sig_make` + возврат из `pop_char_draw` | 7 944 | `pop_cdraw.c` |
| `pop_loadkid` + расчёт координат кадра | 7 410 | `pop_cdraw.c` |
| `obj_x * 8 / 7` — последнее `__divsint` в горячем пути | ~2 400 | `pop_char_draw` |
### P12. G9 — снять временную оснастку [замер: −6 000]
`pop_dbg_b1..b6` в `pop_blit_b` (~400 на блит), `pop_dbg_kind`/`m16`,
`pop_dbg_m5..m15`, счётчик `rd_cnt` в `pop_redraw_needed`.
**Только ПОСЛЕ окончания оптимизации** — без них не мерить.
### P13. Крупные рефакторинги — брать, только если понадобится ещё запас
- **C7 / backlog §5-6: objtable + отложенные таблицы back/mid/fore.** У
оригинала «посетить тайл» стоит копейки, потому что таблицы только копят
записи, а рисует один `draw_table()` в конце. У нас блит идёт сразу из
обхода, и fore-проход отдельный НА КАЖДОГО персонажа.
- **backlog §4: единый проход по тайлам вместо трёх** (`pop_redraw_needed`,
`pop_process_trobs`, `pop_fore_over_char`) и семь счётчиков причин
перерисовки вместо одного `kind`.
- **G6: меньше блитов в `RD_FLOOR`**, **G7: `mob_tick_one` в file-scope**.
---
## 4. Сводка: что сколько даёт в 11/15
| # | приём | эффект | тип оценки | риск |
|---|---|---:|---|---|
| P1 | чомпер: только anim-слой | −160 000 | модель | низкий |
| P3 | Кид перестанет будиться | −70 000 | модель | следствие P1 |
| P2 | логика двух Char | 40 000 … 70 000 | гипотеза | ? |
| P4 | накладные блита (4 правки) | −28 000 | модель | низкий |
| P5 | `loose_tick` без плит | −28 000 | замер | низкий |
| P6 | цикл `process_trobs` | 20 000 … 40 000 | гипотеза | ? |
| P10 | футпринт из физики | −23 000 | замер | средний |
| P11 | мелочи (4 штуки) | −26 000 | замер | низкий |
| P12 | снять оснастку | −6 000 | замер | нулевой |
| P7 | раскол `draw_tile` | 0 здесь (50 000 в 13/23) | оценка | средний |
| P8/P9 | HEAL-WIDTH / G8 | 0 здесь (сцены с плитами) | оценка | низкий/средний |
Верхняя часть списка (P1 + P3 + P4 + P5) — **около 286 000 из 801 768, то
есть 36 % работы кадра**, и вся она низкого риска. Этого хватит, чтобы
сцена ушла с 1,86 растрового кадра до ~1,2 — но НЕ хватит, чтобы период
кадра упал с 4 растров до 3: для этого работа должна уложиться в 430 000,
то есть нужны ещё ~90 000 сверху (P2 или P6).