Files
Sprinter-SDCC/applications/PoP/docs/resident_budget.md
T
snark13 b6699b3aef Разрез pop_tile: холодная половина в банк 5 (−1788 Б резидента)
pop_tile.c был крупнейшим жильцом резидента (5972 Б кода).  Целиком он не
уедет — его const-таблицы читают банки 2, 3, 7 и 8, а таблица в чужом банке
не видна.  Поэтому разрез, а не перенос.

Отбор ЗАМЕРОМ, а не по смыслу: каждая функция посчитана брейкпоинтом-
счётчиком в MAME — сколько вызовов в кадре покоя и сколько в кадре полной
перерисовки комнаты (форсируется читом +/-).  Порог — пик не больше 3.
Проверено на ДВУХ тайлсетах, подземелье и дворец: pop_mem_b рисует
композитный кусок и мог оказаться дворцовым, но и там 0 вызовов.

Уехало: pop_mem_b, pop_cd_hit (+hit_rect), pop_cd_hit_slot, pop_cd_init,
pop_cd_clear, pop_t_win_set/clear, pop_bar_black, pop_heal_off,
pop_potion_flask, pop_room_set_above/below.
Осталось: pop_blit_b со статиками (184 вызова на редрав), pop_cd_touch
(198), pop_tile_code (296), pop_wall_modifier (101), pop_env_b (73),
pop_tile_mod (70), все таблицы.  pop_fore_set_clip оставлен намеренно —
88 Б не стоят отказа от прямого вызова из банка 4.

Цена трамплина замерена: 252 такта пролог + 84 эпилог + ~50 у вызывающего
= ~410.  Это ~1000 тактов на кадр покоя (0,2 % работы) и ~3700 на редрав.

blit_b_clip перестал быть static и объявлен в _pop_tile.h: вызов
банк -> резидент прямой, трамплин не нужен, поэтому статик горячей половины
переносить следом не пришлось.

Итог: _CODE 23716 -> 21928, свободно 747 -> 2535 Б (с 129 Б до всех работ).
Проверено в MAME на уровнях 1 и 4, с переходами комнат.

Метод замера, таблица частот и ловушка с данными банка — docs/resident_budget.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 22:26:05 +03:00

7.1 KiB
Raw Blame History

Бюджет резидента W1/W2: как мерить и как освобождать

Резидент huge-режима — окно 0x4100..0xBB00 (стек с 0xBB00): код в W1, данные в W2, между концом данных и стеком остаётся куча. Всё, что туда не влезло, живёт в банках.

Как СМОТРЕТЬ, а не гадать

Карта линкера врёт. File-static SDCC в неё не попадает, и «дырка» между двумя именованными символами приписывается предыдущему целиком. По карте выходило, что у pop_bg 1529 Б данных (на деле 289), а у pop_t_win_clear 1282 Б кода — при том, что это однострочник, а 1282 Б это два статических помощника соседнего pop_blit_b.

Точный источник — объектные файлы: строки A <area> size <n> flags <f> в .rel дают ровный размер каждой области модуля, а S <sym> Def/Ref — кто символ определяет и кто на него ссылается.

Частоту вызовов мерить в MAME счётчиком, а не оценивать по смыслу:

bpset <frame_probe>,1,{printf "F %d ...",temp0,...; temp0=0;...; g}
bpset <func_addr>,1,{temp0=temp0+1; g}

Обязательна канарейка — счётчик заведомо горячей функции в том же прогоне. Дважды спасала: один раз показала, что перехода комнаты в окне замера не было (все нули), другой — что зонды вообще не встали (в zsh set -- $pair НЕ разбивает строку на слова, и адрес уезжал в мусор). Полную перерисовку комнаты форсировать читом +/-, ходьбой ненадёжно.

Сделано

1. malloc вон из резидента (−613 Б)

cbl_open держал malloc/free в мёртвой ветке CBL_UNDERRUN_SILENCE, а линкер тянет .rel целиком — и куча приезжала каждому приложению. Разведены две публичные точки входа (cbl_open / cbl_open_silence) поверх общего _cbl_open_raw; cbl_close больше не зовёт free.

2. Разрез pop_tile: холодная половина в банк 5 (−1788 Б)

pop_tile.c был крупнейшим жильцом резидента (5 972 Б кода). Целиком он не уедет: его const-таблицы (POP_TILE_DIV/MOD, pop_tile_table, таблицы кадров) читают банки 2, 3, 7 и 8, а таблица в чужом банке не видна.

Отбирали ЗАМЕРОМ, на двух тайлсетах (подземелье ур. 1 и дворец ур. 4 — pop_mem_b рисует композитный кусок и мог оказаться дворцовым). Порог — пик не больше 3 вызовов на кадр.

уехало в банк 5 пик/кадр осталось в резиденте пик/кадр
pop_mem_b 0 pop_tile_code 296
pop_cd_hit (+hit_rect) 0 pop_cd_touch 198
pop_t_win_set/clear 0..1 pop_blit_b (+2 статика) 184
pop_heal_off 0..1 pop_wall_modifier 101
pop_potion_flask 0..1 pop_env_b 73
pop_room_set_above/below 1 pop_tile_mod 70
pop_cd_init/clear 1 pop_cd_batch_end 40
pop_bar_black 3 pop_fore_set_clip 2
pop_cd_hit_slot 2..3 все const-таблицы

pop_fore_set_clip (88 Б) оставлен намеренно: не стоит отказа от прямого вызова из банка 4, ради которого он и заводился.

Цена трамплина замерена: 252 такта пролог + 84 эпилог + ~50 на стороне вызывающего = ~410 тактов на вызов. Итого ~1 000 тактов на кадр покоя (0,2 % работы) и ~3 700 на кадр редрава (0,009 растра).

Ключ, почему это безопасно: вызов банк → резидент ПРЯМОЙ, трамплин не нужен (W1/W2 замаплены всегда). Поэтому blit_b_clip просто перестал быть static и объявлен в _pop_tile.h, а не переехал следом за pop_mem_b.

Итог

было стало
_CODE резидента 24 329 21 928
свободно до стека 129 Б 2 535 Б
BANK5 2 080 (13 %) 3 954 (24 %)

Проверено в MAME: уровень 1 (подземелье) и уровень 4 (дворец), переходы комнат читом +, ходьба — фон, факелы, решётки, гобелены, колонны без искажений.

ЛОВУШКА: данные банка в его страницу — НЕ ДЕЛАТЬ без разбора

Отдельная попытка (--bank-data=SRC, коммиты 3545826/9025573) откачена: перенос писучих данных банкового модуля в его 16-КБ страницу давал цветной мусор блоками и ронял DSS.

У pop_trob причина найдена: pop_trob_modif() ВОЗВРАЩАЕТ УКАЗАТЕЛЬ на room_modif[24][30], а зовут её из банков 2, 3, 7 и резидента — после переноса они пишут по 0xC000+ в СВОЮ страницу, поверх чужого кода. Def/Ref-анализ такого не видит: снаружи ссылки на символ нет, есть ссылка на функцию, отдающую его адрес. Но и pop_room, у которого утечки указателя найти не удалось, ломался так же — механизм понят не до конца.

Нулевая инициализация при этом ни при чём: mkexe -p 0 был проверен по образу (прогон нулей 14 304 Б, самый длинный прогон 0xFF — 14).

Перенос КОДА в банк — штатный путь, на нём стоят все наши банки. Ломался именно перенос ДАННЫХ.

Что осталось

  • roomtest.c 2 508 Б и pop_kid.c 2 418 Б — следующие по величине, но оба горячие (главный цикл и play_seq).
  • pop_level.c 956 Б кода + 660 Б данных (из них pop_dl1/pop_dl2 по 256 Б — таблицы дверных связей).
  • BANK7 на 77 %: если понадобится место в нём — выносить pop_redraw.c.