Files
Sprinter-SDCC/applications/PoP/docs/perf_green_phase.md
T
snark13 babd40bc84 Профиль каскада плит ур.13 к.23: три рабочих документа по фазам
Контрольный замер перед оптимизацией (задача OPT-BLIT требовала начать с
него, а не с правок).  Сцена: старт уровня 13, комната 23, шесть плит
сыплются с потолка вразнобой.

Пик логического кадра — 1 437 150 тактов при растровом кадре 430 000, то
есть 5-6 растровых кадров вместо трёх.  По секциям (бюджет 400 000):
синяя 142 830 (в норме), зелёная до 805 000, циан до 632 000.

Что нашлось (всё подтверждено зондами, не гипотезы):

  RDA_CEIL      дрожащая плита-потолок     48 658 x до 6 = 292 000
  RDA_CEIL_GONE запечь колодец            251 023 x до 2 = 619 000
  RD_FLOOR      щебень на месте посадки   198 259 x до 2 = 397 000

Внутри RDA_CEIL: heal 8 491 + draw_tile 39 029, а блитов в этом draw_tile
всего 1,17 на тайл (15-16 тыс.) — то есть ~23 700 тактов чистых накладных.
Подсчёт asm сходится до процентов: у draw_tile 26-байтовый стековый кадр и
513 обращений (ix), при ~46 замеренных тактах на обращение это те же
~23 600.  У blit_b_clip — 22 Б кадра и 211 (ix).

Разбор одного pop_blit_b (157 замеров быстрого пути): atlas_image+w0_map
1 086, ядро libbgi 10 422, pop_cd_touch 4 502 (28%!), unmap+возврат 264.
В mob_render эти три pop_cd_touch к тому же ИЗБЫТОЧНЫ — коридор куска уже
помечен одним вызовом в pop_loose_mob_tick: 81 000 тактов в кадре впустую.

Итог по пиковому кадру: блиты 488 100 (34%), из них «железный» минимум
пикселей ~150 000; остальные ~1 150 000 (80% кадра) — наши накладные.
Узкое место не передача пикселей, а 16-битная арифметика в IX-кадрах.

Ответ на вопрос про uint8_t: просканированы все 109 .atl и наборы
TITLE/PV — максимум по игровому кадру 56x63 (kid3), фон 48x63, спрайты
комнаты принцессы 49x60.  Шире 255 только восемь полноэкранных подложек
титров/сюжета (три 320x200 и пять 256..272), и все рисуются раз на экран.
Значит горячий путь переводится на байтовые габариты целиком, а подложкам —
отдельный банк / прямой libbgi (решение пользователя).

Документы (решение пользователя — вести работу по фазам, чтобы результаты
не теряись между сессиями):

  docs/perf_l13_room23.md  сцена, рецепт воспроизведения, зонды, канал clog,
                           сводка по кадрам, габариты спрайтов
  docs/perf_green_phase.md зелёная фаза: раскладка, позиции G1..G6, журнал
  docs/perf_cyan_phase.md  циан: раскладка, позиции C1..C7, журнал

Отдельно записан рецепт воспроизведения: сцена повторяется ТОЛЬКО
перезапуском программы (ESC → зонды → roomtest).  Возврат в комнату не
годится — упавшая плита уходит в страницу уровня насовсем; рестарт уровня
не годится — гряда доваливается заочно через trob комнаты 17, пока телепорт
уносит Кида; запись отладчиком на бегущей машине затирается тем же trob.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 12:22:21 +03:00

13 KiB
Raw Blame History

ЗЕЛЁНАЯ фаза (слой фона) — анализ и оптимизация

Рабочий документ: живёт между сессиями. Внизу журнал правок — каждая запись с замером до/после. Сцена, рецепт воспроизведения и зонды — perf_l13_room23.md; там же ответ про 8-битные габариты. Парная фаза — perf_cyan_phase.md.

Границы фазы в roomtest.c: от PROF(4) (строка 450) до PROF(6) (строка 620). Содержимое: pop_loose_tick, pop_process_trobs, pop_redraw_needed, шов, смена уровня, сигналы провалов, вспышка.

Бюджет: ≤ 400 000 тактов (растровый кадр = 430 000).

состояние зелёная
покой в комнате 23 35 760
дрожат 6 плит-потолков 366 000
пик каскада (провалы + посадки) 805 000 (2,0× бюджета)

1. Раскладка (замер 2026-08-17, c312e4a, зонды m5/m6/m7 + kind/m16)

1.1 Верхний уровень

участок покой дрожь пик
pop_loose_tick 25 602 35 646 196 152
pop_process_trobs 1 050 1 050 1 050
pop_redraw_needed 924 319 944 737 322
хвост (шов, смена уровня, сигналы, вспышка) 9 336 9 336 11 964

pop_redraw_needed — 91 % фазы на пике.

1.2 pop_loose_tick изнутри (зонды m9..m12)

участок покой пик
два цикла по тайлам (30 + 10 позиций) 11 004 9 852
pop_loose_mob_tick (heal летящих кусков) 7 416 178 494
check_loose_fall_on_kid 6 120 3 480
хвост 324 3 252

pop_loose_mob_tick при шести кусках в воздухе — ≈29 700 такта на кусок. Это heal прошлой позиции (коридор MOB_W × 64) плюс pop_cd_touch на коридор плюс mob_tick_one.

1.3 Цена ОДНОЙ перерисовки по видам (66 + 12 + 12 замеров)

вид штук замерено средняя цена сколько за кадр Σ за кадр
RDA_CEIL — дрожащая плита-потолок 66 48 658 до 6 292 000
RDA_CEIL_GONE — запечь колодец 12 251 023 до 2 619 000
RD_FLOOR — щебень на месте посадки 12 198 259 до 2 397 000

Для масштаба: ОДНА запечка колодца = 58 % растрового кадра, две = 144 %. pop_dbg_rdmax подтверждает состав пика: шесть пометок, все RDA_CEIL.

1.4 RDA_CEIL = 48 658 изнутри (зонды m13/m14/m15)

участок такты
pop_heal_off(x, 0, 64, CEIL_BAND_H=8) 8 491
draw_tile(-1, col) 39 029

Блитов внутри этого draw_tile1,17 на тайл (7 вызовов pop_blit_b на 6 тайлов), то есть 15–16 тыс. тактов. Остальные ~23 700 такта — накладные draw_tile, ни одного пикселя.

1.5 RDA_CEIL_GONE = 251 023 и RD_FLOOR = 198 259

  • pop_ceil_bake_empty(col) — бар + шесть draw_tile (ряд −1 и ряд 0, колонки col1..col+1): 6 × 39 000 ≈ 234 000.
  • pop_floor_bake(row,col) — бар 60×39 от yb+26 + два draw_tile НИЖНЕГО ряда; там стена, то есть wall_pattern — ≈99 000 на тайл.

1.6 Почему draw_tile стоит 39 000 при одном блите

Кодоген. Подсчёт в bank7_pop_room.asm и совпадение с замером до процентов (одно -N(ix) ≈ 19 номинальных тактов ≈ 46 замеренных):

функция стековый кадр обращений (ix) ожидаемо замерено
draw_tile 26 Б 513 ~23 600 ~23 700
pop_ceil_bake_empty 8 ~370
pop_floor_bake 12 ~550
mob_tick_one 16 Б 99 ~4 550 из ~29 700/кусок
mob_draw_pass 18 Б 20 ~900

У draw_tile пятнадцать локалей, объявленных int (code/lcode/lmod/xh/x/dby/dmy/t/lt/rbl_code/rbl_mod/ wmod/base_id/base_yb), — в регистры они не влезли, и функция целиком уехала в стековый кадр. Байтовые по природе из них все, кроме x/dby/dmy (экранные координаты, бывают отрицательными).


2. Способы ускорения

По убыванию отдачи. Оценки — от замеров выше; каждая позиция сверена с SDLPoP/src/seg008.c (правило проекта: механику сверять с оригиналом ДО кодинга).

G1. Окно клипа для ЗАПЕЧКИ — самый крупный кусок

Отдача: 250 000 … −400 000 на пиковый кадр.

pop_ceil_bake_empty и pop_floor_bake восстанавливают ИЗВЕСТНЫЙ прямоугольник (тот, который сами же залили баром), а зовут полный draw_tile, который честно рисует и то, что в этот прямоугольник не попадает. У pop_floor_bake бар — 60×39 от yb+26, а тело дворцовой кладки рисуется НИЖЕ yb+64, то есть вся стена рисуется мимо цели.

В pop_blit_b уже есть дешёвый предфильтр по окну pop_t_fclip_* — он режет кусок ДО atlas_image и gfx_w0_map, то есть почти даром. Достаточно выставлять это окно на время запечки.

Грабли, которые надо обойти: pop_t_fclip_on в pop_blit_b заодно подавляет pop_cd_touch (признак «это fore-проход поверх персонажа»). Для запечки это как раз правильно — она помечает область сама, — но проверить обязательно; если понадобится, ввести отдельный флаг «окно только для отсева».

Сверка с оригиналом: у него та же идея, только через wipetabledraw_tile_wipe(height) кладёт прямоугольник в таблицу, а draw_table рисует уже с известным клипом (seg008:1368, 1373).

G2. Расколоть draw_tile + отдельный лист для ряда −1

Отдача: 39 029 → 18 00020 000 на тайл полосы, то есть −140 000 на кадр дрожания; и она умножается на G1 (шесть вызовов в запечке).

Два шага:

  1. Отдельный draw_tile_aboveroom(col) — дословный порт seg008:01F2. Оригинал для ряда −1 делает РОВНО шесть вещей: draw_tile_floorright, draw_tile_anim_topright, draw_tile_right, draw_tile_bottom(1), draw_loose(1), draw_tile_fore. Ни базы, ни draw_tile_anim, ни draw_tile_anim_right, ни wall_pattern, ни вычисления rbl_* через row+1 > 2. У нас всё это отсекается ветками if (row >= 0) уже ВНУТРИ общего draw_tile — то есть кадр IX, пролог и вся арифметика оплачиваются всегда.
  2. Расколоть общий draw_tile по образцу оригинала (seg008:01C7 — девять узких листьев). Каждый лист получает xh/dby/dmy параметрами (в регистрах), локалей у него единицы, кадра IX нет.

Заодно проверить redraw_needed_above (seg008:02C1): оригинал делает draw_tile_wipe(3) — стирает 32×3, а мы pop_heal_off(x,0,64,8) = 64×8. Площадь в 5 раз больше нужной (8 491 такта), хотя основная цена там — накладные вызова.

G3. blit_b_clip — 22 Б кадра, 211 (ix)

Отдача: 14 088 → ~10 500 на блит. В зелёной фазе ВСЕ блиты клипованные (полоса у потолка выставляет pop_t_clip_top) — при 7–17 блитах это −25 000 … −60 000; в циане и fore-проходе больше.

dx/dy/dw/dh/sx/sy объявлены int. По построению sx/sy/dw/dh ≤ 255 (габарит любого спрайта ≤ 56×63, см. perf_l13_room23.md §5) — их можно сделать uint8_t, оставив 16-битными только экранные dx/dy. Прецедент: тот же приём в pop_blit_b (коммит c312e4a) дал −19 % на клипованном пути и −16 % на быстром, _CODE 42 Б.

Проверка по правилу OPT-BLIT: после правки смотреть пролог в .sprinter-cc-roomtest/pop_tile.asm — исчез ли ld iy,#-22, сколько осталось -N(ix).

G4. mob_tick_one / pop_loose_mob_tick — 29 700 на кусок

Отдача: −30 000 … −48 000 на кадр при шести кусках.

16-байтовый кадр, 99 (ix). Байтовыми могут быть габарит heal-коридора и разность prev_y; x/y — 16-битные. Плюс pop_cd_touch на коридор — один на кусок, 4 502 такта (см. perf_cyan_phase.md §1.3 про его цену).

G5. LOOSE-SHAKE-RUNS — пометки по сменам кадра

Отдача: ~20 % работы фазы, и только на дрожащих плитах. Оценка старая (2026-08-13), не перемерена.

Подробности и арифметика — в roomtest/TASKS_OPEN.md#loose-shake-runs. Коротко: наивный вариант «помечать только на смене кадра» выигрыша НЕ даёт (пять смен × две страницы = те же десять перерисовок); работает только версия «пары и тройки» — две пометки с pages = 2 на фазах 5 и 8. Тот же приём применим к плитам-ПОТОЛКАМ (pop_set_redraw_above), где сейчас идёт пометка каждый кадр отсчёта.

Вопрос открытый: стоит ли неочевидный код такого узкого выигрыша. Браться после G1–G4.

G6. BG-ONCE — heal вместо чёрного бара

Предложение пользователя, см. roomtest/TASKS_OPEN.md#bg-once. Сейчас запечка = «залить чёрным + перерисовать фон», то есть двойная работа; heal по координатам бара делает то же за один проход. Прямо дополняет G1.


3. Что НЕ делать (проверено, отрицательный результат)

  • Не ускорять передачу пикселей — она на пределе железа (3+3 такта на байт через акселератор), memory blit_cost_model. В пиковом кадре «железный» минимум всех блитов ≈150 000 из 1 437 150.
  • Не откладывать запекание на другой кадр — запечка пишет ОЗУ-копию, из которой восстанавливает heal; отложенная даёт призрак плиты на месте дыры (проверено 2026-08-13).
  • Не батчить смежные колонки в pop_ceil_shake_draw — пробовалось и убрано: плиты стартуют со случайными задержками, в кадре дрожат разрозненные колонки, пробег почти всегда длиной в одну.
  • Не искать проблему в W3-скобке (_bgi_begin/_bgi_end — пять инструкций) и не списывать разброс на прерывания (внутри размерной группы разброс три такта).

4. Журнал правок

дата что сделано зелёная: покой / дрожь / пик коммит
2026-08-17 базовый замер (до правок этой серии) 35 760 / 366 000 / 805 000 c312e4a