mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-10 09:06:36 +00:00
experiment(vegas): vegas_scroll.switch_interval_ms shortens the GIL switch interval during a run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,14 @@ class VegasModeConfig:
|
|||||||
# drawn off the render thread. See docs/OFFSCREEN_RENDERING.md.
|
# drawn off the render thread. See docs/OFFSCREEN_RENDERING.md.
|
||||||
offscreen_prefetch: bool = True
|
offscreen_prefetch: bool = True
|
||||||
|
|
||||||
|
# How long another thread may hold the GIL before the render thread's
|
||||||
|
# request forces it to yield, in ms, while Vegas runs. CPython's default is
|
||||||
|
# 5ms. Plugin rendering on the prefetch thread and plugin updates hold the
|
||||||
|
# GIL in Pillow and Python code, and a frame waiting its turn for 5ms at a
|
||||||
|
# time misses its refresh. 0 leaves the interpreter default alone.
|
||||||
|
# Experimental: measured with scripts/frame_soak.py before it gets a default.
|
||||||
|
switch_interval_ms: float = 0.0
|
||||||
|
|
||||||
# Keep one continuous strip, extending it with the next group of plugins as
|
# Keep one continuous strip, extending it with the next group of plugins as
|
||||||
# the scroll approaches the end, instead of composing a fresh strip and
|
# the scroll approaches the end, instead of composing a fresh strip and
|
||||||
# swapping it in. A swap stops the motion, substitutes every pixel at once
|
# swapping it in. A swap stops the motion, substitutes every pixel at once
|
||||||
@@ -212,6 +220,7 @@ class VegasModeConfig:
|
|||||||
sub_pixel_blend=bool(vegas_config.get('sub_pixel_blend', False)),
|
sub_pixel_blend=bool(vegas_config.get('sub_pixel_blend', False)),
|
||||||
continuous_scroll=vegas_config.get('continuous_scroll', True),
|
continuous_scroll=vegas_config.get('continuous_scroll', True),
|
||||||
offscreen_prefetch=bool(vegas_config.get('offscreen_prefetch', True)),
|
offscreen_prefetch=bool(vegas_config.get('offscreen_prefetch', True)),
|
||||||
|
switch_interval_ms=float(vegas_config.get('switch_interval_ms', 0.0) or 0.0),
|
||||||
extend_threshold_screens=float(
|
extend_threshold_screens=float(
|
||||||
vegas_config.get('extend_threshold_screens', 2.0)),
|
vegas_config.get('extend_threshold_screens', 2.0)),
|
||||||
auto_trim=vegas_config.get('auto_trim', True),
|
auto_trim=vegas_config.get('auto_trim', True),
|
||||||
@@ -254,6 +263,7 @@ class VegasModeConfig:
|
|||||||
'sub_pixel_blend': self.sub_pixel_blend,
|
'sub_pixel_blend': self.sub_pixel_blend,
|
||||||
'continuous_scroll': self.continuous_scroll,
|
'continuous_scroll': self.continuous_scroll,
|
||||||
'offscreen_prefetch': self.offscreen_prefetch,
|
'offscreen_prefetch': self.offscreen_prefetch,
|
||||||
|
'switch_interval_ms': self.switch_interval_ms,
|
||||||
'extend_threshold_screens': self.extend_threshold_screens,
|
'extend_threshold_screens': self.extend_threshold_screens,
|
||||||
'auto_trim': self.auto_trim,
|
'auto_trim': self.auto_trim,
|
||||||
'trim_threshold': self.trim_threshold,
|
'trim_threshold': self.trim_threshold,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Supports three display modes per plugin:
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
from typing import Optional, Dict, Any, List, Callable, TYPE_CHECKING
|
from typing import Optional, Dict, Any, List, Callable, TYPE_CHECKING
|
||||||
@@ -280,6 +281,7 @@ class VegasModeCoordinator:
|
|||||||
# due immediately so the first sample confirms the marquee is up.
|
# due immediately so the first sample confirms the marquee is up.
|
||||||
self._fps_last_health_log = 0.0
|
self._fps_last_health_log = 0.0
|
||||||
self._fps_was_degraded = False
|
self._fps_was_degraded = False
|
||||||
|
self._apply_switch_interval()
|
||||||
|
|
||||||
# Line up the next group immediately, so the first extension is already
|
# Line up the next group immediately, so the first extension is already
|
||||||
# warm rather than stalling the scroll to fetch it.
|
# warm rather than stalling the scroll to fetch it.
|
||||||
@@ -302,6 +304,8 @@ class VegasModeCoordinator:
|
|||||||
self.stats['total_runtime_seconds'] += time.time() - self._start_time
|
self.stats['total_runtime_seconds'] += time.time() - self._start_time
|
||||||
self._start_time = None
|
self._start_time = None
|
||||||
|
|
||||||
|
self._restore_switch_interval()
|
||||||
|
|
||||||
# Cleanup components
|
# Cleanup components
|
||||||
self.render_pipeline.reset()
|
self.render_pipeline.reset()
|
||||||
self.stream_manager.reset()
|
self.stream_manager.reset()
|
||||||
@@ -309,6 +313,23 @@ class VegasModeCoordinator:
|
|||||||
|
|
||||||
logger.info("Vegas mode stopped")
|
logger.info("Vegas mode stopped")
|
||||||
|
|
||||||
|
def _apply_switch_interval(self) -> None:
|
||||||
|
"""Shorten the GIL switch interval for the run; see VegasModeConfig."""
|
||||||
|
ms = self.vegas_config.switch_interval_ms
|
||||||
|
if not ms or ms <= 0:
|
||||||
|
return
|
||||||
|
if getattr(self, '_saved_switch_interval', None) is None:
|
||||||
|
self._saved_switch_interval = sys.getswitchinterval()
|
||||||
|
sys.setswitchinterval(ms / 1000.0)
|
||||||
|
logger.info("Vegas: GIL switch interval %.1fms (was %.1fms)",
|
||||||
|
ms, self._saved_switch_interval * 1000.0)
|
||||||
|
|
||||||
|
def _restore_switch_interval(self) -> None:
|
||||||
|
saved = getattr(self, '_saved_switch_interval', None)
|
||||||
|
if saved is not None:
|
||||||
|
sys.setswitchinterval(saved)
|
||||||
|
self._saved_switch_interval = None
|
||||||
|
|
||||||
def pause(self) -> None:
|
def pause(self) -> None:
|
||||||
"""Pause Vegas mode (for live priority interruption)."""
|
"""Pause Vegas mode (for live priority interruption)."""
|
||||||
with self._state_lock:
|
with self._state_lock:
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""vegas_scroll.switch_interval_ms: applied for a Vegas run, restored after."""
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||||
|
|
||||||
|
from src.vegas_mode.config import VegasModeConfig # noqa: E402
|
||||||
|
from src.vegas_mode.coordinator import VegasModeCoordinator # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def _coordinator(ms):
|
||||||
|
c = VegasModeCoordinator.__new__(VegasModeCoordinator)
|
||||||
|
c.vegas_config = VegasModeConfig(switch_interval_ms=ms)
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
def test_applied_for_the_run_and_restored_after():
|
||||||
|
before = sys.getswitchinterval()
|
||||||
|
c = _coordinator(1.0)
|
||||||
|
try:
|
||||||
|
c._apply_switch_interval()
|
||||||
|
assert abs(sys.getswitchinterval() - 0.001) < 1e-9
|
||||||
|
c._apply_switch_interval() # a second start must not lose the original
|
||||||
|
finally:
|
||||||
|
c._restore_switch_interval()
|
||||||
|
assert sys.getswitchinterval() == before
|
||||||
|
|
||||||
|
|
||||||
|
def test_zero_leaves_the_interpreter_alone():
|
||||||
|
before = sys.getswitchinterval()
|
||||||
|
c = _coordinator(0.0)
|
||||||
|
c._apply_switch_interval()
|
||||||
|
c._restore_switch_interval()
|
||||||
|
assert sys.getswitchinterval() == before
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_from_config():
|
||||||
|
config = VegasModeConfig.from_config(
|
||||||
|
{"display": {"vegas_scroll": {"switch_interval_ms": 1}}})
|
||||||
|
assert config.switch_interval_ms == 1.0
|
||||||
|
assert VegasModeConfig.from_config({"display": {"vegas_scroll": {}}}).switch_interval_ms == 0.0
|
||||||
Reference in New Issue
Block a user