You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Shrink live stats memory + harden profiler self-exclusion (#1043)
Two in-process RAM reductions and two correctness tightens identified
while auditing the overhead of the stitched-stacks timeline:
- __slots__ on PyFrameKey, NativeFrameKey, CombinedStackRun, StackFrame,
StackStats, and _ResolvedNativeFrame. These types are instantiated
per CPU / malloc sample and aggregated into long-lived dicts and the
timeline list. Dropping the per-instance __dict__ takes PyFrameKey
from 664 → 192 bytes and NativeFrameKey from 424 → 72 bytes. Manual
slots tuples rather than slots=True because 3.8 is still supported.
- Per-process frame-key intern caches in scalene_utility (add_stack /
add_combined_stack / add_async_await_run) and scalene_memory_profiler
(malloc-sample StackFrame construction). Before this, each sample
constructed fresh dataclass instances that compared equal; only the
combined_stacks dict de-duplicated them, and the timeline retained
all the redundant objects for the lifetime of the run. Caches are
cleared in Scalene.start() so repeated magic-cell profiling sessions
don't carry state across runs.
- TraceConfig now accepts the canonical path of the installed scalene
package (from os.path.realpath(os.path.dirname(scalene.__file__)))
and uses it as an absolute-path prefix check in should_trace, in
addition to the legacy "immediate parent directory literally named
scalene" heuristic. The prefix check is robust to vendoring, editable
installs under symlinks, and any packaging layout that doesn't name
the directory "scalene". The parent-name check stays as a defensive
fallback. register_files_to_profile / setup_trace_config take the
path as an optional 4th arg to preserve the old call signature.
- Scalene.stop() now always flips p_scalene_done = true, not only when
args.memory is set, so any allocation made while we serialize the
profile to JSON (or via an atypical shutdown path such as
term_signal_handler) is guaranteed not to land in the profile.
set_scalene_done_{true,false} in pywhere.cpp become no-ops when
libscalene isn't preloaded (CPU-only runs), so the unconditional
flip is safe regardless of mode.
Measured on big_workload.py (10s, 3 threads, --memory --stacks):
ScaleneStatistics total 1616 KB -> 215 KB -86.7%
combined_stacks_timeline 1544 KB -> 123 KB -92.0%
combined_stacks 243 KB -> 23 KB -90.5%
avg CombinedStackRun 4688 B -> 1920 B -59.0%
Tests: 397 pytest passed + 19 skipped (full suite), ruff clean,
mypy clean on all 63 source files. tests/test_coverup_78.py updated
for the new 4-arg register_files_to_profile signature.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 commit comments