PoP roomtest: LCG оригинала на ассемблере — точность без потери скорости
Возврат к БИТ-В-БИТ генератору оригинала по умолчанию (POP_PRANDOM_EXACT=1): по нему проще отлаживать и сверять картинку с эталоном. Чтобы это не стоило процента бюджета, сам шаг LCG переписан на Z80-ассемблере — единственное место в порте, где это сделано, с явного разрешения. Приём: 214013 = ((((1<<1)+1)<<2 + 1)<<4 + 1)<<10 - 3 — схема Горнера по РАЗРЕЖЕННОЙ записи константы. Вместо 12 сложений (по числу единиц в 0x343FD) — 17 удвоений, три сложения и одно вычитание; величина 3*s, нужная в конце, попадается по дороге на втором шаге. Проверка в ДВА этапа: - схема на хосте: horner(s) == s*214013+2531011 на 3 000 000 сидов; - сама asm-транскрипция на живой машине: breakpoint на pop_prandom, 11 последовательных состояний сида из MAME — каждый переход совпал с s*214013+2531011 бит-в-бит. Замер, комната 3, 175 кадров (медиана кадра / prandom->torch_draw): C, бит-в-бит (16-бит половины) 403 632 / 10 933 C, xorshift16 + шаг Вейля 397 986 / 7 927 asm, бит-в-бит 400 800 / 9 331 То есть asm вернул половину разрыва (2 832 такта за кадр), сохранив совместимость с эталоном. Ветка xorshift оставлена под -DPOP_PRANDOM_EXACT=0 как запасной ход — брать её имеет смысл, только если не хватит последних 2 800 тактов. Итог оптимизационного круга: 416 154 -> 400 800 (0.968 -> 0.932 кадра). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,21 +31,26 @@ int8_t pop_y_to_row(int16_t y);
|
||||
* нельзя.
|
||||
*
|
||||
* ДВА генератора, выбор через POP_PRANDOM_EXACT (см. pop_geom.c):
|
||||
* 0 (по умолчанию) — xorshift16 + шаг Вейля. Быстрый: ни одного
|
||||
* умножения. Последовательность ОТЛИЧАЕТСЯ от SDLPoP, то есть другая
|
||||
* раскладка кладки и другие броски в боёвке — статистически
|
||||
* эквивалентные, но не те же самые;
|
||||
* 1 — LCG оригинала (s*214013+2531011) бит-в-бит. Для сверки картинки
|
||||
* с эталоном. Дороже: даже посчитанный 16-битными половинами (без
|
||||
* __mullong, который один стоил 8 430 тактов — замер в MAME) он
|
||||
* обходится в ~7 000 тактов на вызов.
|
||||
* 1 (по умолчанию) — LCG оригинала (s*214013+2531011) БИТ-В-БИТ: та же
|
||||
* раскладка кладки и те же броски, что в SDLPoP, поэтому по картинке
|
||||
* можно сверяться с эталоном;
|
||||
* 0 — xorshift16 + шаг Вейля, без единого умножения. Дешевле ровно на
|
||||
* 5 646 тактов за кадр, 1.3 % бюджета (замер A/B в MAME, комната 3,
|
||||
* 175 кадров: 403 632 против 397 986). Последовательность другая —
|
||||
* статистически эквивалентная, но не та же.
|
||||
*
|
||||
* Состояние — две 16-битные половины, а не unsigned long: иначе SDCC на
|
||||
* умножении зовёт __mullong. */
|
||||
* Обе версии считают 16-битными половинами, а не unsigned long: на 32-бит
|
||||
* умножении SDCC зовёт __mullong, и это 8 430 тактов на вызов (замер в
|
||||
* MAME, breakpoint на __mullong).
|
||||
*
|
||||
* ЗАДЕЛ: разрыв между версиями определяется не алгоритмом, а кодогенерацией
|
||||
* SDCC — те же генераторы на Z80-ассемблере укладываются в 86..148 тактов.
|
||||
* Если процент понадобится, сначала переписать в asm ОРИГИНАЛЬНЫЙ LCG (там
|
||||
* умножение на константу), а менять генератор — только если и этого мало. */
|
||||
typedef struct { uint16_t lo, hi; } pop_rnd_t;
|
||||
|
||||
#ifndef POP_PRANDOM_EXACT
|
||||
#define POP_PRANDOM_EXACT 0
|
||||
#define POP_PRANDOM_EXACT 1
|
||||
#endif
|
||||
|
||||
uint16_t pop_prandom(pop_rnd_t *seed, uint16_t maxv);
|
||||
|
||||
Reference in New Issue
Block a user