Repository navigation
Return large run results through shared memory instead of the pipe - #108
Open
kavyawadhwa134 wants to merge 6 commits into
Open
kavyawadhwa134 wants to merge 6 commits into
kavyawadhwa134 wants to merge 6 commits into
Conversation
Application.run spent 40-81% of its wall time pickling the awkward output through multiprocessing.Pipe (~60-80 MB/s on macOS). The child now writes the arrays' raw buffers (ak.to_buffers) into one shared-memory block and sends only forms, lengths and offsets; the parent unlinks the block, copies it out and rebuilds the arrays with ak.from_buffers. Results under 1 MB of array data, and results whose block can't be allocated (posix_fallocate guards against a full /dev/shm on Linux), still go through the pipe. The child unlinks the block if sending fails; blocks orphaned by a crash are removed by the shared resource tracker. em_calo on an M2: 13/13/19 -> 22/48/43 events/s at 1/4/8 threads; the transfer share drops from 38/75/75% to 2/8/16%.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The parent now names each block (a per-application prefix plus the message counter) and unlinks it after every call, so a block written by a worker that is killed before sending its descriptor no longer waits for the resource tracker at interpreter exit. GEANT4_PYTHON_APPLICATION_SHM_MIN_BYTES overrides the 1 MB threshold, and _transfer.stats counts results by path, so tests can force and check either path.
- seeded run forced through shared memory vs forced through the pipe gives identical arrays (fields, nesting, dtypes, parameters) - results over 1 MB use shared memory, small ones the pipe - no blocks left in /dev/shm after a normal run, a run that raises, a failed unpack, or the worker killed with SIGKILL mid-transfer - an 8-thread result much larger than a 64 MB /dev/shm falls back to the pipe without SIGBUS; CI remounts /dev/shm at 64 MB on Ubuntu/Python 3.10 and fails if that test is skipped
From the GPU-offload profiling (Phase 0). Updated for the shared-memory transfer: the transfer row is no longer pickle-only, and each run records whether results came through shared memory or the pipe.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Profiling (session wonderful-dirac-dd7816,
tools/profile_breakdown.py) showed that pickling the awkward output ofApplication.runthroughmultiprocessing.Pipetook 40–81% of the wall time on macOS (~60–80 MB/s, 190–310 MB per 8-thread run). This had to be fixed before a GPU offload could be evaluated fairly.What
geant4_python_application/_transfer.py:pack): if a result holds ≥ 1 MB ofak.Arraydata, it callsak.to_packed+ak.to_buffers(the same steps awkward's own pickling uses) and copies every buffer (64-byte aligned) into onemultiprocessing.shared_memoryblock. Only the forms, lengths, offsets, behavior and the block name/size go through the pipe.unpack): attaches, unlinks right away, copies the block out, closes it, and rebuilds the arrays withak.from_buffers.application.py: the child callspackbeforepipe.send(anddiscards the block if the send fails)._send_and_recvcallsunpack.posix_fallocatereserves the pages up front, so a too-small/dev/shm(64 MB by default in Docker) makes it fall back instead of crashing the child with SIGBUS.tests/test_transfer.py: round-trip of strings, records, nested lists, option types andwith_name; unlink after unpack;discard; allocation-failure fallback; and an end-to-end run large enough to use shared memory, at 0 and 2 threads.Benchmark (em_calo, Apple M2 8 GB, Geant4 11.4.3 MT, medians of 5)
Application.runG4 transport is now 50–91% of public-API wall time, compared with 18–58% before. The 8-thread samples are noisy (1.56–5.21 s), which fits memory pressure on an 8 GB machine.
Testing
Follow-up commits
g4<random>_<counter>) and unlinks it after every call. A block written by a worker that is SIGKILLed before sending is removed right away instead of at interpreter exit.GEANT4_PYTHON_APPLICATION_SHM_MIN_BYTESforces either path (0/ huge)._transfer.statscounts results by path. A test-only pause runs after the block is written./dev/shmafter a normal run, a run that raises, a failed unpack, or a mid-transfer SIGKILL;/dev/shmfalls back to the pipe without SIGBUS./dev/shmat 64 MB, rerunstests/test_transfer.py, and fails if the small-shm test is skipped.tools/profile_breakdown.pycommitted, updated for the new transfer.🤖 Generated with Claude Code