From cbdc22cc165b5cb42c52a3865f7e34b33fe8ddd2 Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Thu, 30 Jul 2026 10:34:10 +0200 Subject: [PATCH] Improved CPU knobs --- gnommo/cache.py | 67 ++++++++++++++++++++++++++---------------- gnommo/preprocessor.py | 4 +-- gnommo/renderer.py | 2 +- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/gnommo/cache.py b/gnommo/cache.py index 849828f..466d23b 100644 --- a/gnommo/cache.py +++ b/gnommo/cache.py @@ -20,32 +20,52 @@ _assets_config: Optional[dict] = None _perf_config: Optional[dict] = None -def get_ffmpeg_thread_count() -> int: - """Return FFmpeg thread count based on [performance] cpu_limit in ~/.gnommo.conf. - - cpu_limit is a fraction of logical CPUs (e.g. 0.8 = 80%). - Defaults to 1 when not configured, which is safe on memory-constrained machines. - - Example ~/.gnommo.conf: - [performance] - cpu_limit = 0.8 - """ +def _load_perf_config() -> dict: + """Read and cache the [performance] section of ~/.gnommo.conf.""" global _perf_config - if _perf_config is None: - config_path = Path.home() / ".gnommo.conf" - _perf_config = {} - if config_path.exists(): - cfg = configparser.ConfigParser() - cfg.read(config_path) - if cfg.has_option("performance", "cpu_limit"): + if _perf_config is not None: + return _perf_config + + _perf_config = {} + config_path = Path.home() / ".gnommo.conf" + if config_path.exists(): + cfg = configparser.ConfigParser() + cfg.read(config_path) + for key in ("cpu_limit_preprocess", "cpu_limit_render", "cpu_limit"): + if cfg.has_option("performance", key): try: - _perf_config["cpu_limit"] = float( - cfg.get("performance", "cpu_limit") - ) + _perf_config[key] = float(cfg.get("performance", key)) except ValueError: pass + if cfg.has_option("performance", "render_chunk_slides"): + try: + _perf_config["render_chunk_slides"] = int( + cfg.get("performance", "render_chunk_slides") + ) + except ValueError: + pass + return _perf_config - cpu_limit = _perf_config.get("cpu_limit") + +def get_ffmpeg_thread_count(stage: str = "preprocess") -> int: + """Return the FFmpeg thread count for a pipeline stage from ~/.gnommo.conf. + + Preprocessing and rendering scale differently, so they read separate CPU + fractions of the logical core count: + + [performance] + cpu_limit_preprocess = 0.8 # throughput-bound; safe at high parallelism + cpu_limit_render = 0.15 # -filter_complex spawns swscaler threads per + # layer and OOMs at high core counts + + `stage` is "preprocess" or "render". The legacy single `cpu_limit` key is the + fallback for either stage when its specific key is absent. Each value is a + fraction of logical CPUs (0.8 = 80%); defaults to 1 thread when nothing is + configured, which is safe on memory-constrained machines. + """ + cfg = _load_perf_config() + key = "cpu_limit_render" if stage == "render" else "cpu_limit_preprocess" + cpu_limit = cfg.get(key, cfg.get("cpu_limit")) if cpu_limit is None: return 1 cpu_count = os.cpu_count() or 1 @@ -62,10 +82,7 @@ def get_render_chunk_size() -> Optional[int]: [performance] render_chunk_slides = 15 """ - global _perf_config - if _perf_config is None: - get_ffmpeg_thread_count() # populates _perf_config - val = _perf_config.get("render_chunk_slides") + val = _load_perf_config().get("render_chunk_slides") if val is None: return None try: diff --git a/gnommo/preprocessor.py b/gnommo/preprocessor.py index ce78f8f..74829b4 100644 --- a/gnommo/preprocessor.py +++ b/gnommo/preprocessor.py @@ -21,10 +21,10 @@ from typing import Union, Optional def _tc() -> str: - """Return FFmpeg thread count string from ~/.gnommo.conf [performance] cpu_limit.""" + """FFmpeg thread count for preprocessing (~/.gnommo.conf cpu_limit_preprocess).""" from .cache import get_ffmpeg_thread_count - return str(get_ffmpeg_thread_count()) + return str(get_ffmpeg_thread_count("preprocess")) # Number of parallel workers for chunk processing diff --git a/gnommo/renderer.py b/gnommo/renderer.py index 6c4298b..7da701b 100644 --- a/gnommo/renderer.py +++ b/gnommo/renderer.py @@ -447,7 +447,7 @@ def build_ffmpeg_command(plan: RenderPlan, output_path: Path) -> list[str]: # thread per core no matter what — the real cause of the render-stage memory blowup. from .cache import get_ffmpeg_thread_count - _tc = str(get_ffmpeg_thread_count()) + _tc = str(get_ffmpeg_thread_count("render")) cmd.extend( ["-threads", _tc, "-filter_threads", _tc, "-filter_complex_threads", _tc] )