Files
ComfyAssets-ComfyUI_PromptM…/utils/native_memory.py
T
Vito 8a81114dfc perf(images): run image work on two own pools and trim after jobs (#523) (#582)
* perf(images): read PNG text without decoding pixels (#523)

Pillow's PngImageFile.text loads every pixel to reach text chunks written
after IDAT. The scan's metadata batch and the image monitor now read PNG
text through utils/png_text.py, which runs Pillow's own chunk handlers
after the first IDAT and seeks over the pixel data.

The legacy .text read is the floor: a differential test compares both
readers on every fixture image, a curated corpus and a seeded fuzz. The
only difference is a file whose pixel data is undecodable or cut short:
it now keeps the text read before its pixels instead of reading nothing.
Animated PNGs still use .text.

* test(images): cover the text budget, XMP and layout guard of the PNG text walk (#523)

* perf(images): run image work on two own pools and trim after jobs (#523)

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).

* fix(images): start no image thread after the teardown; join retired job pools (#523)
2026-11-01 01:24:27 +00:00

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