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

15 KiB
Raw Blame History

Реестр оптимизаций: всё отложенное, в одном списке

Собрано 2026-08-19 из perf_green_phase.md (G1-G9), perf_cyan_phase.md (C1-C7), perf_backlog.md (позиции 1-7), ../roomtest/TASKS_OPEN.md (HEAL-WIDTH) и из свежего разбора сцены 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 §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).