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>
7.1 KiB
Бюджет резидента 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.c2 508 Б иpop_kid.c2 418 Б — следующие по величине, но оба горячие (главный цикл иplay_seq).pop_level.c956 Б кода + 660 Б данных (из нихpop_dl1/pop_dl2по 256 Б — таблицы дверных связей).- BANK7 на 77 %: если понадобится место в нём — выносить
pop_redraw.c.