Ускорение холостого хода: указательный обход вместо arr[i] в горячих циклах

Замер зелёного блока (комната 23 уровня 13, MAME, такты эмулятора) показал,
что 86% его стоимости в ПОКОЕ — это pop_loose_tick, который не делает ничего.

Причина — кодоген SDCC, подтверждена чтением .asm.  С `int pos` и записью
pop_loose_modif[pos] компилятор держал счётчик в IX-фрейме, каждую итерацию
заново складывал 16-битный адрес элемента, клал его в локал и тут же
вычитывал обратно парами `pop bc / pop hl / push hl / push bc`.  40 холостых
итераций (30 тайлов + 10 потолков) стоили 27 936 тактов.  То же в
pop_loose_mob_tick: запись mobs[i] заставляла умножать i на sizeof(mob_t)=15
заново под КАЖДОЕ поле (.active/.clean/.x/.y), 14 пустых слотов — 35 790.

Правка — обход указателем, счётчик uint8_t, пустые слоты отсеиваются в
вызывающем цикле (а не гардом внутри mob_tick_one, до которого надо ещё
дойти).  Холостая итерация стала `ld a,(de) / or a,a / jp Z` — три
инструкции вместо дюжины с обращениями к памяти.

Результат (такты MAME, холостой кадр):
  циклы по тайлам      27 936 -> 9 852   (2,8x)
  pop_loose_mob_tick   35 790 -> 7 416   (4,8x)
  pop_loose_tick       70 866 -> 24 384  (2,9x)
  ЗЕЛЁНЫЙ БЛОК         82 242 -> 35 760  (2,3x)
Банки ужались: BANK3 -90 Б, BANK7 -74 Б.

Все 8 наборов tests-host проходят; комната 23 в MAME рисуется корректно.

Плюс ВРЕМЕННАЯ оснастка замера (маркеры m9..m16, pop_dbg_kind) — она же
показала, что пик зелёного блока сидит НЕ в тряске плит, а в запекании
тайлов; разбор продолжается, оснастку снять перед закрытием темы.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 23:08:05 +03:00
parent 50e4eda2ad
commit f89b7dd0d2
7 changed files with 175 additions and 41 deletions
+33
View File
@@ -113,3 +113,36 @@ void pop_dbg_m1(void) { }
void pop_dbg_m2(void) { }
void pop_dbg_m3(void) { }
void pop_dbg_m4(void) { }
/* ВРЕМЕННО (разбор зелёного блока 2026-08-13): состав перерисовок в САМОМ
* тяжёлом кадре. pop_redraw_needed считает вызовы по видам, и если кадр
* оказался тяжелее прежнего максимума — сохраняет расклад сюда.
* Индексы 0..7 — POP_RD_*, 8..9 — POP_RDA_* (полоса у потолка). */
uint8_t pop_dbg_rdmax[10];
uint8_t pop_dbg_rdmax_tot;
/* ВРЕМЕННО (замер внутренностей 2026-08-13): m5..m8 — границы внутри
* pop_ceil_shake_draw (heal / draw_tile) и mob_render (heal / блиты). */
void pop_dbg_m5(void) { }
void pop_dbg_m6(void) { }
void pop_dbg_m7(void) { }
void pop_dbg_m8(void) { }
/* ВРЕМЕННО (разбор pop_loose_tick 2026-08-13): m9..m12 — границы ВНУТРИ
* самого pop_loose_tick. Он банковый, но зовёт резидент W1 напрямую,
* так что брейкпоинты по этим адресам однозначны. */
void pop_dbg_m9(void) { }
void pop_dbg_m10(void) { }
void pop_dbg_m11(void) { }
void pop_dbg_m12(void) { }
/* ВРЕМЕННО (разбор pop_ceil_shake_draw 2026-08-13): heal / draw_tile. */
void pop_dbg_m13(void) { }
void pop_dbg_m14(void) { }
void pop_dbg_m15(void) { }
/* ВРЕМЕННО (разбор pop_redraw_needed по ВИДАМ 2026-08-13): вид приезжает в
* A (__sdcccall(1), uint8 arg1), брейкпоинт на первой инструкции логирует
* его вместе с тактами — так видно цену КАЖДОГО вида, а не число пометок. */
void pop_dbg_kind(uint8_t k) { (void)k; }
void pop_dbg_m16(void) { }