355. Кэширование пикселей кадрового буфера nametable
Кэшировать повторные пиксели кадрового буфера nametable по точным графическим входным данным.
Урок 355 из 356 · tests/chapter_14_optimizations/test_355_cache_nametable_framebuffer_pixels.py
Файл для обновления
emulator/rendering/nametable_renderer.pyЗачем нужен этот шаг
Фронтенд запрашивает у эмулятора полное фоновое изображение для каждого отображаемого кадра. Формирование одного логического изображения nametable требует декодирования данных паттернов, выбора палитры тайлов из таблицы атрибутов и записи 256×240 RGB-пикселей. Прокрутка может изменять видимую область соседних nametable без изменения исходных графических данных внутри каждого из них, поэтому последовательные кадры часто запрашивают одно и то же ресурсоёмкое исходное изображение.
При 60 FPS один полный кадр располагает примерно 16,67 мс на шаги CPU/PPU, отрисовку фона и спрайтов, загрузку кадрового буфера и вывод на экран. Перестроение 61 440 пикселей из неизменённых байтов расходует этот ограниченный бюджет, не создавая новой информации. Ограниченный кэш с адресацией по содержимому превращает повторные исходные отрисовки в поиск по неизменяемым пикселям плюс копирование списка, высвобождая время кадра для работы, которая действительно изменилась.
Имена и обязанности
- No existing function is renamed.
- nametable_with_attributes_to_framebuffer() remains unchanged. It is still the
lower-level operation that performs the expensive render on a cache miss.
- nametable_with_palette_ram_to_framebuffer() keeps its public name and signature,
but its body changes into an ownership-preserving wrapper around cached pixels.
- _cached_nametable_with_palette_ram_pixels() is a new private helper containing
the old public function's palette-building and rendering work.Полная реализация
# functools.lru_cache is already imported for Test 354. Add this import if the
# previous step is being reproduced independently:
from functools import lru_cache
@lru_cache(maxsize=8)
def _cached_nametable_with_palette_ram_pixels(
nametable_bytes: bytes,
attribute_table: bytes,
pattern_table_bytes: bytes,
palette_ram: bytes,
) -> tuple[RGBColor, ...]:
background_palettes = build_background_palettes_from_palette_ram(
palette_ram
)
framebuffer = nametable_with_attributes_to_framebuffer(
nametable_bytes,
attribute_table,
pattern_table_bytes,
background_palettes,
)
# The cache must own immutable data so callers cannot poison later hits.
return tuple(framebuffer.pixels)
def nametable_with_palette_ram_to_framebuffer(
nametable_bytes: bytes,
attribute_table: bytes,
pattern_table_bytes: bytes,
palette_ram: bytes,
) -> Framebuffer:
cached_pixels = _cached_nametable_with_palette_ram_pixels(
nametable_bytes,
attribute_table,
pattern_table_bytes,
palette_ram,
)
# Preserve the historical mutable ownership contract.
return Framebuffer(
width=BACKGROUND_WIDTH,
height=BACKGROUND_HEIGHT,
pixels=list(cached_pixels),
)Где редактировать
Замените существующее тело nametable_with_palette_ram_to_framebuffer() обёрткой, показанной выше, и добавьте рядом новый приватный кэшированный хелпер. Не оставляйте старые операторы построения палитры и рендеринга в публичной обёртке, поскольку это выполнило бы ресурсоёмкую работу до обращения к кэшу.
Граница проверки типов
nametable_with_attributes_to_framebuffer() возвращает Framebuffer, поэтому прямой возврат этого вызова из _cached_nametable_with_palette_ram_pixels() нарушает объявленный тип возврата tuple[RGBColor, ...]. Сначала привяжите Framebuffer к показанной выше локальной переменной, затем верните tuple(framebuffer.pixels). Не меняйте аннотацию кэшированного хелпера на Framebuffer, поскольку это привело бы к кэшированию изменяемого публичного состояния.
Важные инварианты
- все четыре точных неизменяемых байтовых входа участвуют в ключе кэша
- равное содержимое байтов переиспользует работу, даже если объекты имеют разную идентичность
- изменение любого графического входа вызывает промах кэша
- кэшированные пиксели неизменяемы
- публичные объекты
Framebufferи списки пикселей имеют независимое владение - кэш ограничен восемью записями
Влияние на производительность
Попадания в кэш исключают декодирование паттернов, реконструкцию палитры, обход тайлов и генерацию RGB-пикселей. Промахи кэша сохраняют исходный путь отрисовки, так что изменение любых визуальных входных данных по-прежнему даёт свежие и корректные пиксели. Ограничение размера контролирует использование памяти, сохраняя недавно переиспользованные изображения nametable.
Распространённое заблуждение
Не кэшируйте и не возвращайте один изменяемый экземпляр Framebuffer. Механизм ускорения заключается в переиспользовании неизменяемого содержимого пикселей, а не в разделении изменяемого владения кадром.
Запустить этот урок
uv run pytest tests/chapter_14_optimizations/test_355_cache_nametable_framebuffer_pixels.py -v