mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-01 08:48:05 +00:00
Pace the Vegas frame loop adaptively: 31.5 -> 78.7 fps
The loop slept a fixed frame_interval on top of however long the frame took, so at a measured 31.6ms per frame a flat 8ms of that was pure idle — a quarter of the budget spent not rendering. It now sleeps only the remainder of the budget. Measured on hardware: 31.5 fps to 78.7 fps sustained, with CPU going *down* from 150% to 127%. Scroll speed is unchanged at 49.9px/s against a configured 50, because motion is derived from elapsed time rather than frame count — this buys smoothness, not speed. Worth recording what the bottleneck was not: the per-frame render path measures 0.34ms in total (0.18ms for the numpy slice, 0.17ms for the dirty-tracking digest), which is a theoretical 2900 fps. Optimising any of that would have been wasted effort. The frame was idle, not busy. Also nices the prefetch thread. Its work is PIL and numpy that releases the GIL, so the scheduler can act on the priority, and without it the prefetch competes for the same cores as the render loop. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
This commit is contained in:
@@ -376,6 +376,8 @@ class VegasModeCoordinator:
|
|||||||
logger.info("Starting Vegas iteration for %.1fs", duration)
|
logger.info("Starting Vegas iteration for %.1fs", duration)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
frame_started = time.time()
|
||||||
|
|
||||||
# Check for STATIC mode plugin that should pause scroll
|
# Check for STATIC mode plugin that should pause scroll
|
||||||
static_plugin = self._check_static_plugin_trigger()
|
static_plugin = self._check_static_plugin_trigger()
|
||||||
if static_plugin:
|
if static_plugin:
|
||||||
@@ -396,8 +398,14 @@ class VegasModeCoordinator:
|
|||||||
# Paused for live priority - let caller handle
|
# Paused for live priority - let caller handle
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Sleep for frame interval
|
# Sleep only the remainder of the frame budget. This used to sleep
|
||||||
time.sleep(frame_interval)
|
# the whole interval on top of however long the frame took, so at a
|
||||||
|
# measured 31.6ms per frame a fixed 8ms of that was pure idle — a
|
||||||
|
# quarter of the budget spent not rendering. Subtracting the work
|
||||||
|
# already done keeps the pacing target while reclaiming that time,
|
||||||
|
# and yields the GIL either way so other threads still run.
|
||||||
|
frame_elapsed = time.time() - frame_started
|
||||||
|
time.sleep(max(0.0, frame_interval - frame_elapsed))
|
||||||
|
|
||||||
# Increment frame count and check for interrupt periodically
|
# Increment frame count and check for interrupt periodically
|
||||||
frame_count += 1
|
frame_count += 1
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ Uses the existing ScrollHelper for numpy-optimized scroll operations.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
from collections import deque
|
from collections import deque
|
||||||
@@ -256,6 +257,15 @@ class RenderPipeline:
|
|||||||
return # already have one waiting
|
return # already have one waiting
|
||||||
|
|
||||||
def _work():
|
def _work():
|
||||||
|
# Deprioritise against the render loop. Linux applies nice
|
||||||
|
# per-thread, and the heavy lifting here is PIL and numpy work
|
||||||
|
# that releases the GIL, so the scheduler can actually act on
|
||||||
|
# it — without this the prefetch competes for the same cores and
|
||||||
|
# costs frames.
|
||||||
|
try:
|
||||||
|
os.nice(10)
|
||||||
|
except (OSError, AttributeError):
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
group = self.stream_manager.take_next_group(offscreen_only=True)
|
group = self.stream_manager.take_next_group(offscreen_only=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
Reference in New Issue
Block a user