Category
Performance / robustness of a hot path
Component
Host runtime (src/common/hierarchical/, python/simpler/worker.py)
Description
Both ends of the parent-child mailbox wait by polling, and neither can be made both low-latency and cheap that way.
| side |
mechanism |
cost |
parent (LocalMailboxEndpoint::run) |
sleep_for(50us) per TASK_DONE poll |
up to a 50 us quantum added to every dispatch |
child (_run_mailbox_loop) |
unbounded busy-spin |
a full core held for the child's whole lifetime |
The parent's sleep is on the critical path of every dispatch and dominates the round trip. Measured on aarch64 Linux, 3 sub workers, no-op callables:
sequential run(): median 150 us p90 152 us max 176 us
burst (200 subs): 106 us per task
Note sleep_for(50us) does not cost 50 us — time.sleep(50e-6) measures ~102 us on this box, so the quantum is roughly double what the constant says.
A blocking wakeup primitive fixes both sides at once: zero CPU while waiting, single-digit-microsecond wake, and no interval to tune. This is now what codestyle.md rule 5 requires of any dispatch-path wait, so worker_manager.cpp is a recorded deviation rather than a style preference.
Location
Priority
medium
Additional context
Candidate primitive. A pipe per mailbox direction looks better than a PTHREAD_PROCESS_SHARED condvar in the mailbox shm, despite slightly worse wake latency:
|
pipe |
pshared condvar |
| wake latency |
~5-20 us (unverified on this box) |
~3-10 us |
| macOS support |
POSIX, identical both ends |
supported, but needs ctypes into libc from Python |
| GIL |
os.read releases it |
pthread_cond_wait needs care |
| peer death |
poll() with a timeout |
a child dying while holding the mutex hangs the parent; no robust mutexes on macOS |
The repo has no blocking primitive today — futex, sem_wait, PTHREAD_PROCESS_SHARED, eventfd, pthread_cond are all zero hits across src/, python/ and docs/.
Constraint to preserve. The parent currently samples child liveness every 200 poll iterations via waitpid(WNOHANG), so a child that dies without publishing TASK_DONE cannot hang the loop forever. poll() with a timeout keeps that behaviour; a bare blocking read() would lose it.
Measure before implementing. The 5-20 us figure is a general expectation, not measured here. The gating experiment is one number: pipe write-to-wake latency on this host versus the current 50 us quantum. If the gain is not real on this hardware, the design should change before any code does.
What this is not. Not a fix for #1493 (children surviving parent death). With a blocking wait an orphan would cost 0% CPU, but it would still be a leaked process holding fds and shm, so the two are complementary.
An earlier attempt to pace the child's poll with a sleep instead (#1491) was closed: measurement showed it traded 17% of burst dispatch throughput for the CPU saving, which is the trade the rule now forbids.
Category
Performance / robustness of a hot path
Component
Host runtime (
src/common/hierarchical/,python/simpler/worker.py)Description
Both ends of the parent-child mailbox wait by polling, and neither can be made both low-latency and cheap that way.
LocalMailboxEndpoint::run)sleep_for(50us)perTASK_DONEpoll_run_mailbox_loop)The parent's sleep is on the critical path of every dispatch and dominates the round trip. Measured on aarch64 Linux, 3 sub workers, no-op callables:
Note
sleep_for(50us)does not cost 50 us —time.sleep(50e-6)measures ~102 us on this box, so the quantum is roughly double what the constant says.A blocking wakeup primitive fixes both sides at once: zero CPU while waiting, single-digit-microsecond wake, and no interval to tune. This is now what
codestyle.mdrule 5 requires of any dispatch-path wait, soworker_manager.cppis a recorded deviation rather than a style preference.Location
src/common/hierarchical/worker_manager.cpp—LocalMailboxEndpoint::run, thesleep_for(std::chrono::microseconds(50))in theTASK_DONEpoll looppython/simpler/worker.py—_run_mailbox_loop(single site since Refactor: fold the three child mailbox loops into one state machine #1494; all three child loops route through it)Priority
medium
Additional context
Candidate primitive. A pipe per mailbox direction looks better than a
PTHREAD_PROCESS_SHAREDcondvar in the mailbox shm, despite slightly worse wake latency:os.readreleases itpthread_cond_waitneeds carepoll()with a timeoutThe repo has no blocking primitive today —
futex,sem_wait,PTHREAD_PROCESS_SHARED,eventfd,pthread_condare all zero hits acrosssrc/,python/anddocs/.Constraint to preserve. The parent currently samples child liveness every 200 poll iterations via
waitpid(WNOHANG), so a child that dies without publishingTASK_DONEcannot hang the loop forever.poll()with a timeout keeps that behaviour; a bare blockingread()would lose it.Measure before implementing. The 5-20 us figure is a general expectation, not measured here. The gating experiment is one number: pipe write-to-wake latency on this host versus the current 50 us quantum. If the gain is not real on this hardware, the design should change before any code does.
What this is not. Not a fix for #1493 (children surviving parent death). With a blocking wait an orphan would cost 0% CPU, but it would still be a leaked process holding fds and shm, so the two are complementary.
An earlier attempt to pace the child's poll with a sleep instead (#1491) was closed: measurement showed it traded 17% of burst dispatch throughput for the CPU saving, which is the trade the rule now forbids.