Image work no longer spreads over the default executor's threads, where glibc kept an image-sized malloc arena per thread. - Interactive image work (view previews behind their gate, workspace card facts, both inspector routes) runs on the API's fixed pm-image pool of 3 threads; the on-demand preview pool is merged into it. - map_parallel (scan metadata, thumbnail and convert jobs, the legacy gallery generation, capture previews) shares one long-lived pm-worker pool of Worker threads, reused between jobs. The package teardown stops it within its bound (part "workers"). - After a scan, thumbnail or convert job, glibc's malloc_trim(0) returns free pages once; a no-op on Windows, macOS and musl. - tools/perf/memory_drive.py measures RSS, heap, threads and arenas under the #523 load on the disposable browser test server (manual only).
66 lines
1.8 KiB
Python
66 lines
1.8 KiB
Python
"""Give glibc's free heap pages back to the OS after a long image job (#523).
|
|
|
|
Image work (decode, resize, encode) leaves image-sized free blocks in the
|
|
malloc arenas of the threads that did it; glibc keeps those pages resident.
|
|
``release_free_memory`` calls ``malloc_trim(0)`` once, which returns the free
|
|
pages of every arena and changes no allocator setting (no ``mallopt``, no
|
|
``MALLOC_ARENA_MAX``: ComfyUI owns this process's allocator).
|
|
|
|
glibc only: on Windows, macOS and musl (Alpine) it does nothing. It never
|
|
raises; a missing or failing ``malloc_trim`` is a quiet ``False``.
|
|
"""
|
|
|
|
import ctypes
|
|
import os
|
|
import sys
|
|
import threading
|
|
|
|
_lock = threading.Lock()
|
|
_trim = None
|
|
_looked_up = False
|
|
|
|
|
|
def _glibc() -> bool:
|
|
"""Whether this process runs on glibc (musl has no CS_GNU_LIBC_VERSION)."""
|
|
if not sys.platform.startswith("linux"):
|
|
return False
|
|
try:
|
|
version = os.confstr("CS_GNU_LIBC_VERSION")
|
|
except (AttributeError, ValueError, OSError):
|
|
return False
|
|
return bool(version) and version.startswith("glibc")
|
|
|
|
|
|
def _malloc_trim():
|
|
"""glibc's ``malloc_trim``, looked up once; None where there is none."""
|
|
global _trim, _looked_up
|
|
with _lock:
|
|
if not _looked_up:
|
|
_looked_up = True
|
|
_trim = _lookup()
|
|
return _trim
|
|
|
|
|
|
def _lookup():
|
|
if not _glibc():
|
|
return None
|
|
try:
|
|
trim = ctypes.CDLL(None).malloc_trim
|
|
except (OSError, AttributeError):
|
|
return None
|
|
trim.argtypes = [ctypes.c_size_t]
|
|
trim.restype = ctypes.c_int
|
|
return trim
|
|
|
|
|
|
def release_free_memory() -> bool:
|
|
"""Return free heap pages to the OS once (glibc); True when it ran."""
|
|
trim = _malloc_trim()
|
|
if trim is None:
|
|
return False
|
|
try:
|
|
trim(0)
|
|
except Exception: # a foreign libc symbol: never fail the caller's job
|
|
return False
|
|
return True
|