zoo-bench - 2026-10-11 - AMD EPYC 7763 64-Core Processor

zoo-framework 0.6.0
benchmark

Same-caliber comparison against 5 baselines: apscheduler, asyncio_pool, bare_thread, process_pool, thread_pool. Every number was re-measured on this machine by zoo-bench and published per tier, including the tiers the framework under test loses.

raw data: results/zoo-framework-0.6.0/20261011T091515Z.json

zoo-bench report

Run environment

itemvalue
CPU modelAMD EPYC 7763 64-Core Processor
logical cores4
platformLinux-x86_64
operating systemLinux 6.17.0-1022-azure (#22-Ubuntu SMP Mon Jul 27 17:24:03 UTC 2026)
PythonCPython 3.13.16
interpreter/opt/hostedtoolcache/Python/3.13.16/x64/bin/python
GILdefault
framework under test (distribution metadata)0.6.0
framework under test (module __version__)0.1.1-beta (footnote only, not the version criterion)
drive-surface generation of the framework under testprevious-generation drive surface (workers is a writable attribute; the completion signal comes from the framework's internal callback)
harnesszoo-bench 0.0.1 @ 9a42be24ac43f75e2e5d7b16434f91ba3d0717b6
command to reproducezoo-bench run --framework zoo-framework==0.6.0

data: Run environment

Run environment

Run environment (cont.)

dist_version (the distribution metadata) is the source of truth; module_version is only a footnote

data: Run environment

zoo-bench report

Workload

  • composition: one unit of work = JSON serialize + deserialize + string concatenation, repeated until the target duration is reached; the tiers are declared by body_tiers_us in matrix.yaml
  • the workload is a stand-in, not a real trace: its shape resembles business actions such as 'take a frame of data -> serialize -> parse -> transform strings', but this repository holds no real trace, so every conclusion is conditional on that

data: Workload

zoo-bench report

Measurement methodology

  • the body duration is instrumented within the same run: the body measures itself internally and reports back; framework overhead = end-to-end - it. No cross-run subtraction - two runs differ in state (caches, frequencies, scheduling noise), so subtracting introduces systematic bias.
  • warmups are excluded from statistics: each unit first runs 3 warmup rounds that are discarded, then only the following 30 measured rounds are counted; the sample count equals 'measured rounds x concurrency'.
  • absolute durations are not comparable across runs: they only show the order of magnitude; across versions/machines read the relative quantities within the same run (overhead share, multiples against the baselines), which are insensitive to overall speed.

data: Measurement methodology

zoo-bench report

Conclusion summary

  • Once the framework under test zoo runs a body of about 10000 microseconds or more, its framework overhead drops below 15% of end-to-end; below that tier, the main price of choosing it is the framework overhead itself.
  • At concurrency 1, within the measured tiers apscheduler is always faster than the framework under test - tiers like these are exactly the openly published unfavourable data of this report.
  • At concurrency 4, within the measured tiers apscheduler is always faster than the framework under test - tiers like these are exactly the openly published unfavourable data of this report.

data: Conclusion summary

Conclusion summary

Conclusion summary (cont.)

  • At concurrency 16, within the measured tiers apscheduler is always faster than the framework under test - tiers like these are exactly the openly published unfavourable data of this report.
  • At concurrency 64, within the measured tiers apscheduler is always faster than the framework under test - tiers like these are exactly the openly published unfavourable data of this report.
  • At concurrency 1, within the measured tiers asyncio_pool is always faster than the framework under test - tiers like these are exactly the openly published unfavourable data of this report.

data: Conclusion summary

Conclusion summary

the rest of this section's details are in the full report

For one conclusion per slide, only the first 2 slides of this section are shown; the remaining 10 slides of per-unit numbers, percentiles and raw samples sit in the same section of the full report, verbatim checkable.

full report: the "Conclusion summary" section of report.md in the same directory

Conclusion summary - framework overhead share

5.28%

Framework overhead share of zoo at body tier 10000 microseconds.
5 baselines are lower at this tier: apscheduler, asyncio_pool, bare_thread, process_pool, thread_pool.
same-tier comparison: apscheduler 3.41%, asyncio_pool 1.51%, bare_thread 1.31%, process_pool 3.30%, thread_pool 0.64%

data: zoo-bench "framework overhead share" at concurrency 1, body tier 10000 microseconds

Dimension: Latency percentiles and jitter

Latency percentiles and jitter at concurrency 1

concurrency 1 - end-to-end / concurrency

body tier 40 microseconds
zoo
606.25 microseconds
apscheduler
293.74 microseconds
asyncio_pool
145.49 microseconds
bare_thread
143.75 microseconds
process_pool
336.43 microseconds
thread_pool
74.84 microseconds
body tier 300 microseconds
zoo
867.95 microseconds
apscheduler
559.04 microseconds
asyncio_pool
389.99 microseconds
bare_thread
399.35 microseconds
process_pool
606.27 microseconds
thread_pool
338.26 microseconds
framework under testbaselinebar length = multiple relative to the framework under test; 1.0 is parity; longer is worse

data: zoo-bench "Latency percentiles and jitter" at concurrency 1 - per-unit numbers and percentiles are in the same section of report.md

Dimension: Latency percentiles and jitter

Latency percentiles and jitter at concurrency 1 (cont.)

concurrency 1 - end-to-end / concurrency

body tier 2700 microseconds
zoo
3.263 milliseconds
apscheduler
2.989 milliseconds
asyncio_pool
2.617 milliseconds
bare_thread
2.690 milliseconds
process_pool
3.015 milliseconds
thread_pool
2.669 milliseconds
body tier 10000 microseconds
zoo
10.505 milliseconds
apscheduler
9.889 milliseconds
asyncio_pool
9.708 milliseconds
bare_thread
9.552 milliseconds
process_pool
9.702 milliseconds
thread_pool
9.577 milliseconds
framework under testbaselinebar length = multiple relative to the framework under test; 1.0 is parity; longer is worse

data: zoo-bench "Latency percentiles and jitter" at concurrency 1 - per-unit numbers and percentiles are in the same section of report.md

zoo-bench report

Dimension: Latency percentiles and jitter

end-to-end / concurrency; percentiles and relative spread are given in the same table - a metric that only gives the median is not acceptable

concurrencybody tier (microseconds)zooapschedulerasyncio_poolbare_threadprocess_poolthread_pool
140606.25 microseconds293.74 microseconds145.49 microseconds143.75 microseconds336.43 microseconds74.84 microseconds
1300867.95 microseconds559.04 microseconds389.99 microseconds399.35 microseconds606.27 microseconds338.26 microseconds
127003.263 milliseconds2.989 milliseconds2.617 milliseconds2.690 milliseconds3.015 milliseconds2.669 milliseconds
11000010.505 milliseconds9.889 milliseconds9.708 milliseconds9.552 milliseconds9.702 milliseconds9.577 milliseconds
440611.17 microseconds267.20 microseconds153.05 microseconds158.85 microseconds192.86 microseconds95.11 microseconds
4300866.98 microseconds526.01 microseconds428.07 microseconds415.41 microseconds283.82 microseconds361.48 microseconds
427003.307 milliseconds3.003 milliseconds2.800 milliseconds2.693 milliseconds1.390 milliseconds2.676 milliseconds
41000010.700 milliseconds10.132 milliseconds9.695 milliseconds9.872 milliseconds4.608 milliseconds9.867 milliseconds
1640610.25 microseconds226.27 microseconds138.37 microseconds171.89 microseconds161.53 microseconds92.31 microseconds
16300878.71 microseconds484.41 microseconds395.42 microseconds423.77 microseconds278.33 microseconds353.51 microseconds

data: Dimension: Latency percentiles and jitter

Dimension: Latency percentiles and jitter

Dimension: Latency percentiles and jitter (cont.)

concurrencybody tier (microseconds)zooapschedulerasyncio_poolbare_threadprocess_poolthread_pool
1627003.306 milliseconds2.977 milliseconds2.744 milliseconds2.704 milliseconds1.410 milliseconds2.790 milliseconds
161000010.596 milliseconds10.332 milliseconds9.964 milliseconds9.972 milliseconds4.636 milliseconds10.146 milliseconds
6440565.92 microseconds223.95 microseconds120.73 microseconds170.05 microseconds196.54 microseconds89.40 microseconds
64300842.59 microseconds507.24 microseconds371.91 microseconds425.03 microseconds300.97 microseconds355.32 microseconds
6427003.323 milliseconds2.949 milliseconds2.832 milliseconds2.764 milliseconds1.354 milliseconds2.788 milliseconds
641000010.636 milliseconds10.585 milliseconds10.189 milliseconds9.526 milliseconds4.629 milliseconds10.289 milliseconds

per-unit values (percentiles, spread, raw per-round samples) are in 'Appendix: all values' at the end.

data: Dimension: Latency percentiles and jitter

Dimension: Framework overhead share

Framework overhead share at concurrency 1

concurrency 1 - framework overhead

body tier 40 microseconds
zoo
88.13%
apscheduler
80.50%
asyncio_pool
66.60%
bare_thread
64.52%
process_pool
83.87%
thread_pool
40.99%
body tier 300 microseconds
zoo
62.15%
apscheduler
46.08%
asyncio_pool
25.72%
bare_thread
26.40%
process_pool
49.32%
thread_pool
12.38%
framework under testbaselinebar length = multiple relative to the framework under test; 1.0 is parity; longer is worse

data: zoo-bench "Framework overhead share" at concurrency 1 - per-unit numbers and percentiles are in the same section of report.md

Dimension: Framework overhead share

Framework overhead share at concurrency 1 (cont.)

concurrency 1 - framework overhead

body tier 2700 microseconds
zoo
17.04%
apscheduler
8.91%
asyncio_pool
4.87%
bare_thread
4.18%
process_pool
11.36%
thread_pool
1.76%
body tier 10000 microseconds
zoo
5.28%
apscheduler
3.41%
asyncio_pool
1.51%
bare_thread
1.31%
process_pool
3.30%
thread_pool
0.64%
framework under testbaselinebar length = multiple relative to the framework under test; 1.0 is parity; longer is worse

data: zoo-bench "Framework overhead share" at concurrency 1 - per-unit numbers and percentiles are in the same section of report.md

zoo-bench report

Dimension: Framework overhead share

overhead = end-to-end / concurrency - the body duration measured within the same run (no cross-run subtraction). cells with -: that concurrency exceeds this machine's parallel capacity; the body's self-reported duration contains scheduling waits from oversubscription and is not comparable with the row's end-to-end per task, so no overhead figure is given (end-to-end and throughput live in their own dimensions)

concurrencybody tier (microseconds)zooapschedulerasyncio_poolbare_threadprocess_poolthread_pool
14088.13%80.50%66.60%64.52%83.87%40.99%
130062.15%46.08%25.72%26.40%49.32%12.38%
1270017.04%8.91%4.87%4.18%11.36%1.76%
1100005.28%3.41%1.51%1.31%3.30%0.64%
44089.54%79.29%64.75%67.82%51.69%48.45%
430062.78%39.72%24.92%29.04%-14.55%

data: Dimension: Framework overhead share

Dimension: Framework overhead share

Dimension: Framework overhead share (cont.)

concurrencybody tier (microseconds)zooapschedulerasyncio_poolbare_threadprocess_poolthread_pool
4270019.63%10.01%4.93%4.60%-2.23%
410000------
1640------
16300------
162700------
1610000------
6440------
64300------
642700------
6410000------

per-unit values (percentiles, spread, raw per-round samples) are in 'Appendix: all values' at the end.

data: Dimension: Framework overhead share

Dimension: Throughput and concurrency scaling

Throughput and concurrency scaling at concurrency 1

concurrency 1 - throughput

body tier 40 microseconds
zoo
1,649 tasks/s
apscheduler
3,404 tasks/s
asyncio_pool
6,873 tasks/s
bare_thread
6,956 tasks/s
process_pool
2,972 tasks/s
thread_pool
13,363 tasks/s
body tier 300 microseconds
zoo
1,152 tasks/s
apscheduler
1,789 tasks/s
asyncio_pool
2,564 tasks/s
bare_thread
2,504 tasks/s
process_pool
1,649 tasks/s
thread_pool
2,956 tasks/s
framework under testbaselinebar length = multiple relative to the framework under test; 1.0 is parity; longer is worse

data: zoo-bench "Throughput and concurrency scaling" at concurrency 1 - per-unit numbers and percentiles are in the same section of report.md

Dimension: Throughput and concurrency scaling

Throughput and concurrency scaling at concurrency 1 (cont.)

concurrency 1 - throughput

body tier 2700 microseconds
zoo
306 tasks/s
apscheduler
335 tasks/s
asyncio_pool
382 tasks/s
bare_thread
372 tasks/s
process_pool
332 tasks/s
thread_pool
375 tasks/s
body tier 10000 microseconds
zoo
95 tasks/s
apscheduler
101 tasks/s
asyncio_pool
103 tasks/s
bare_thread
105 tasks/s
process_pool
103 tasks/s
thread_pool
104 tasks/s
framework under testbaselinebar length = multiple relative to the framework under test; 1.0 is parity; longer is worse

data: zoo-bench "Throughput and concurrency scaling" at concurrency 1 - per-unit numbers and percentiles are in the same section of report.md

zoo-bench report

Dimension: Throughput and concurrency scaling

throughput = concurrency / median end-to-end; whether it keeps rising with concurrency is the scaling behaviour

concurrencybody tier (microseconds)zooapschedulerasyncio_poolbare_threadprocess_poolthread_pool
1401649.53404.36873.26956.32972.413362.6
13001152.11788.82564.22504.11649.42956.3
12700306.5334.5382.1371.7331.7374.6
11000095.2101.1103.0104.7103.1104.4
4406544.914970.026136.125180.520740.742055.6
43004613.77604.49344.39629.014093.411065.7
427001209.41331.91428.51485.52878.61494.9
410000373.8394.8412.6405.2868.1405.4
164026218.870711.5115629.293082.799052.0173329.2
1630018208.433030.140462.937756.157486.645260.1
1627004839.35375.25831.55917.611345.15735.0

data: Dimension: Throughput and concurrency scaling

Dimension: Throughput and concurrency scaling

Dimension: Throughput and concurrency scaling (cont.)

concurrencybody tier (microseconds)zooapschedulerasyncio_poolbare_threadprocess_poolthread_pool
16100001510.01548.61605.91604.53451.01577.0
6440113090.9285777.7530094.2376362.9325635.1715882.7
6430075956.6126173.2172085.1150577.9212645.4180117.2
64270019258.521702.322599.723154.047269.422956.6
64100006017.56046.26281.46718.213826.16220.2

per-unit values (percentiles, spread, raw per-round samples) are in 'Appendix: all values' at the end.

data: Dimension: Throughput and concurrency scaling

zoo-bench report

Dimension: Overhead attribution

scope: splits the per-task end-to-end into submit side / handoff / body / return and, at the same tier and concurrency, compares each segment against every baseline, answering 'which segment the excess falls on'. It is diagnostic, not the main evidence: it samples a small set of tiers, and does not compare total overhead against the baselines. A negative handoff is not an error: it means the submit call had not returned yet while the body was already running - schemes that count thread creation as submit side behave this way (measured: bare_thread handoff -343 microseconds). **The return segment includes waiting for th

e completion signal** (the last stretch before draining returns);

it shares the main measurement's scope

data: Dimension: Overhead attribution

Dimension: Overhead attribution

Dimension: Overhead attribution (cont.)

status: ok

this dimension is a diagnosis, not the main evidence: the sampled tiers and concurrencies are a fixed small batch, not the whole matrix. The concurrency ceiling is the machine's parallel capacity - beyond it, what is measured is contention;

sampled tiers (microseconds): 40, 10000; concurrency: [1, 4]; this machine's parallel capacity: 4

data: Dimension: Overhead attribution

Dimension: Overhead attribution

the rest of this section's details are in the full report

For one conclusion per slide, only the first 2 slides of this section are shown; the remaining 16 slides of per-unit numbers, percentiles and raw samples sit in the same section of the full report, verbatim checkable.

full report: the "Dimension: Overhead attribution" section of report.md in the same directory

zoo-bench report

Dimension: The cost of scheduling semantics

scope: this dimension uses an internal switch comparison (same workload, semantics all off vs. enabled one by one) and does not compare across schemes - a bare writeup has no counterpart for priority / timeout / retry, so it can only answer 'what is this framework's semantics worth', not 'how much more expensive than a bare writeup'

status: not_measured (the conclusion was reached on the 'previous' drive-surface generation installed during this measurement)

on the version under test, none of the enumerated semantic items is measurable: either its action is not implemented, or its layer cannot execute the reactor. Per design D7's escape hatch, such items are dropped instead of being replaced with an approximate status

data: Dimension: The cost of scheduling semantics

Dimension: The cost of scheduling semantics

Dimension: The cost of scheduling semantics (cont.)

semantics itemlayerhow probedmeasurableevidence / reason
timeoutdispatch layerbehavioral probe (driven repeatedly through scheduling rounds; measures the moment the worker leaves the in-flight table)nothe judgment exists but its action is not implemented: this probe drives through scheduling rounds and the probe worker declares a loop, so the rounds did run - a missing reap can only mean the judgment itself has no action. Enabling it would just add one subtraction and measure nothing of the semantics' cost
priorityevent layerstructural probe (checks whether the symbols required for execution exist)noexecuting reactors on the event layer requires a perform symbol: EventWorker._execute calls reactor.perform (workers/event_worker.py), while EventReactor only defines execute (reactor/event_reactor.py) - a matched reactor would raise AttributeError at once; EventPriorityCalculator exists (fifo/node/event_fifo_node.py) but is not wired into the drain loop - 'sort by priority' in workers/event_worker.py is only a comment
retryevent layerstructural probe (checks whether the symbols required for execution exist)noexecuting reactors on the event layer requires a perform symbol: EventWorker._execute calls reactor.perform (workers/event_worker.py), while EventReactor only defines execute (reactor/event_reactor.py) - a matched reactor would raise AttributeError at once; retry_times defaults to 0, a clean off state, but its trigger is 'no reactor matched' - and the reactor path itself cannot execute

this conclusion was reached on the version under test and its coordinates hold for that version only; after appending a version to matrix.yaml, re-run this probe

data: Dimension: The cost of scheduling semantics

zoo-bench report

Dimension: Native task line: three execution forms of the same subject

scope: this line is measured on its own: it enters neither the main matrix's pivot tables nor its crossings, and its numbers must not be read against the baseline adapters listed there

verdict: not_applicable

the framework under test has no such capability (symbols not found: adaptive_params, dual_arm_worker, native_params, native_task_worker) - this line does not apply to this version, and that is not a failure

equivalence gate: not run (the probe did not reach it)

loaded task modbus_rtu.parse_response - Modbus RTU response-frame parsing: the input is a bare RTU ADU response frame (no transport framing), and the tier's frame bytes are its length

data: Dimension: Native task line: three execution forms of the same subject

Dimension: Native task line: three execution forms of the same subject

Dimension: Native task line: three execution forms of the same subject (cont.)

tiers R1, R8, R125 (frame bytes 7, 21, 255); concurrencies 1, 4, 16, 64; each cell runs 100 warm-up plus 100 measured rounds, one child process per concurrency (4 in all)

data: Dimension: Native task line: three execution forms of the same subject

zoo-bench report

tiers where the framework under test is faster

Tiers where the framework under test is ahead (the two sides differ by more than that tier's band). This comes from the same within-run comparison as the openly published unfavourable data - one side takes the faster tiers, the other the slower ones, so both cover the same ground and neither can pick its own tiers. This section being empty is not a failure: it should be empty when nothing is distinguishable or when the framework loses everywhere.

no tier within the measured set has the framework under test ahead.

data: tiers where the framework under test is faster

Openly published unfavourable data - summary

the framework under test is slower at 75 tiers, across 5 baselines.

apscheduler is faster at 13 tiers

worst 2.70x (concurrency 16 at 40 microseconds). All tiers: concurrency 1 at 40 microseconds, concurrency 1 at 300 microseconds, concurrency 1 at 2700 microseconds, concurrency 1 at 10000 microseconds, concurrency 4 at 40 microseconds, concurrency 4 at 300 microseconds, concurrency 4 at 10000 microseconds, concurrency 16 at 40 microseconds, concurrency 16 at 300 microseconds, concurrency 16 at 2700 microseconds, concurrency 64 at 40 microseconds, concurrency 64 at 300 microseconds, concurrency 64 at 2700 microseconds

asyncio_pool is faster at 16 tiers

worst 4.69x (concurrency 64 at 40 microseconds). All tiers: concurrency 1 at 40 microseconds, concurrency 1 at 300 microseconds, concurrency 1 at 2700 microseconds, concurrency 1 at 10000 microseconds, concurrency 4 at 40 microseconds, concurrency 4 at 300 microseconds, concurrency 4 at 2700 microseconds, concurrency 4 at 10000 microseconds, concurrency 16 at 40 microseconds, concurrency 16 at 300 microseconds, concurrency 16 at 2700 microseconds, concurrency 16 at 10000 microseconds, concurrency 64 at 40 microseconds, concurrency 64 at 300 microseconds, concurrency 64 at 2700 microseconds, concurrency 64 at 10000 microseconds

bare_thread is faster at 16 tiers

worst 4.22x (concurrency 1 at 40 microseconds). All tiers: concurrency 1 at 40 microseconds, concurrency 1 at 300 microseconds, concurrency 1 at 2700 microseconds, concurrency 1 at 10000 microseconds, concurrency 4 at 40 microseconds, concurrency 4 at 300 microseconds, concurrency 4 at 2700 microseconds, concurrency 4 at 10000 microseconds, concurrency 16 at 40 microseconds, concurrency 16 at 300 microseconds, concurrency 16 at 2700 microseconds, concurrency 16 at 10000 microseconds, concurrency 64 at 40 microseconds, concurrency 64 at 300 microseconds, concurrency 64 at 2700 microseconds, concurrency 64 at 10000 microseconds

process_pool is faster at 15 tiers

worst 3.78x (concurrency 16 at 40 microseconds). All tiers: concurrency 1 at 40 microseconds, concurrency 1 at 300 microseconds, concurrency 1 at 10000 microseconds, concurrency 4 at 40 microseconds, concurrency 4 at 300 microseconds, concurrency 4 at 2700 microseconds, concurrency 4 at 10000 microseconds, concurrency 16 at 40 microseconds, concurrency 16 at 300 microseconds, concurrency 16 at 2700 microseconds, concurrency 16 at 10000 microseconds, concurrency 64 at 40 microseconds, concurrency 64 at 300 microseconds, concurrency 64 at 2700 microseconds, concurrency 64 at 10000 microseconds

thread_pool is faster at 15 tiers

worst 8.10x (concurrency 1 at 40 microseconds). All tiers: concurrency 1 at 40 microseconds, concurrency 1 at 300 microseconds, concurrency 1 at 2700 microseconds, concurrency 1 at 10000 microseconds, concurrency 4 at 40 microseconds, concurrency 4 at 300 microseconds, concurrency 4 at 2700 microseconds, concurrency 16 at 40 microseconds, concurrency 16 at 300 microseconds, concurrency 16 at 2700 microseconds, concurrency 16 at 10000 microseconds, concurrency 64 at 40 microseconds, concurrency 64 at 300 microseconds, concurrency 64 at 2700 microseconds, concurrency 64 at 10000 microseconds

data: zoo-bench "published unfavorable data" - per-tier details follow on later slides and in report.md

zoo-bench report

Openly published unfavourable data

Tiers where the framework under test is behind (the two sides differ by more than that tier's band). A report missing this part is not acceptable - a report that only shows where it wins gives the reader every reason to distrust it. Tiers whose difference is smaller than the band are not here but in the 'no winner' section: that is the verdict's outcome, not a place to hide things.

baselineconcurrencybody tier (microseconds)gapmedian of the framework under test
apscheduler140apscheduler is 2.06x faster than zoo606.25 microseconds
asyncio_pool140asyncio_pool is 4.17x faster than zoo606.25 microseconds
bare_thread140bare_thread is 4.22x faster than zoo606.25 microseconds
process_pool140process_pool is 1.80x faster than zoo606.25 microseconds
thread_pool140thread_pool is 8.10x faster than zoo606.25 microseconds
apscheduler1300apscheduler is 1.55x faster than zoo867.95 microseconds

data: Openly published unfavourable data

Openly published unfavourable data

Openly published unfavourable data (cont.)

baselineconcurrencybody tier (microseconds)gapmedian of the framework under test
asyncio_pool1300asyncio_pool is 2.23x faster than zoo867.95 microseconds
bare_thread1300bare_thread is 2.17x faster than zoo867.95 microseconds
process_pool1300process_pool is 1.43x faster than zoo867.95 microseconds
thread_pool1300thread_pool is 2.57x faster than zoo867.95 microseconds
apscheduler12700apscheduler is 1.09x faster than zoo3.263 milliseconds
asyncio_pool12700asyncio_pool is 1.25x faster than zoo3.263 milliseconds
bare_thread12700bare_thread is 1.21x faster than zoo3.263 milliseconds
thread_pool12700thread_pool is 1.22x faster than zoo3.263 milliseconds
apscheduler110000apscheduler is 1.06x faster than zoo10.505 milliseconds
asyncio_pool110000asyncio_pool is 1.08x faster than zoo10.505 milliseconds
bare_thread110000bare_thread is 1.10x faster than zoo10.505 milliseconds
process_pool110000process_pool is 1.08x faster than zoo10.505 milliseconds
thread_pool110000thread_pool is 1.10x faster than zoo10.505 milliseconds

data: Openly published unfavourable data

Openly published unfavourable data

Openly published unfavourable data (cont.)

baselineconcurrencybody tier (microseconds)gapmedian of the framework under test
apscheduler440apscheduler is 2.29x faster than zoo611.17 microseconds
asyncio_pool440asyncio_pool is 3.99x faster than zoo611.17 microseconds
bare_thread440bare_thread is 3.85x faster than zoo611.17 microseconds
process_pool440process_pool is 3.17x faster than zoo611.17 microseconds
thread_pool440thread_pool is 6.43x faster than zoo611.17 microseconds
apscheduler4300apscheduler is 1.65x faster than zoo866.98 microseconds
asyncio_pool4300asyncio_pool is 2.03x faster than zoo866.98 microseconds
bare_thread4300bare_thread is 2.09x faster than zoo866.98 microseconds
process_pool4300process_pool is 3.05x faster than zoo866.98 microseconds
thread_pool4300thread_pool is 2.40x faster than zoo866.98 microseconds
asyncio_pool42700asyncio_pool is 1.18x faster than zoo3.307 milliseconds
bare_thread42700bare_thread is 1.23x faster than zoo3.307 milliseconds
process_pool42700process_pool is 2.38x faster than zoo3.307 milliseconds

data: Openly published unfavourable data

Openly published unfavourable data

Openly published unfavourable data (cont.)

baselineconcurrencybody tier (microseconds)gapmedian of the framework under test
thread_pool42700thread_pool is 1.24x faster than zoo3.307 milliseconds
apscheduler410000apscheduler is 1.06x faster than zoo10.700 milliseconds
asyncio_pool410000asyncio_pool is 1.10x faster than zoo10.700 milliseconds
bare_thread410000bare_thread is 1.08x faster than zoo10.700 milliseconds
process_pool410000process_pool is 2.32x faster than zoo10.700 milliseconds
apscheduler1640apscheduler is 2.70x faster than zoo610.25 microseconds
asyncio_pool1640asyncio_pool is 4.41x faster than zoo610.25 microseconds
bare_thread1640bare_thread is 3.55x faster than zoo610.25 microseconds
process_pool1640process_pool is 3.78x faster than zoo610.25 microseconds
thread_pool1640thread_pool is 6.61x faster than zoo610.25 microseconds
apscheduler16300apscheduler is 1.81x faster than zoo878.71 microseconds
asyncio_pool16300asyncio_pool is 2.22x faster than zoo878.71 microseconds
bare_thread16300bare_thread is 2.07x faster than zoo878.71 microseconds

data: Openly published unfavourable data

Openly published unfavourable data

Openly published unfavourable data (cont.)

baselineconcurrencybody tier (microseconds)gapmedian of the framework under test
process_pool16300process_pool is 3.16x faster than zoo878.71 microseconds
thread_pool16300thread_pool is 2.49x faster than zoo878.71 microseconds
apscheduler162700apscheduler is 1.11x faster than zoo3.306 milliseconds
asyncio_pool162700asyncio_pool is 1.21x faster than zoo3.306 milliseconds
bare_thread162700bare_thread is 1.22x faster than zoo3.306 milliseconds
process_pool162700process_pool is 2.34x faster than zoo3.306 milliseconds
thread_pool162700thread_pool is 1.19x faster than zoo3.306 milliseconds
asyncio_pool1610000asyncio_pool is 1.06x faster than zoo10.596 milliseconds
bare_thread1610000bare_thread is 1.06x faster than zoo10.596 milliseconds
process_pool1610000process_pool is 2.29x faster than zoo10.596 milliseconds
thread_pool1610000thread_pool is 1.04x faster than zoo10.596 milliseconds
apscheduler6440apscheduler is 2.53x faster than zoo565.92 microseconds
asyncio_pool6440asyncio_pool is 4.69x faster than zoo565.92 microseconds

data: Openly published unfavourable data

Openly published unfavourable data

Openly published unfavourable data (cont.)

baselineconcurrencybody tier (microseconds)gapmedian of the framework under test
bare_thread6440bare_thread is 3.33x faster than zoo565.92 microseconds
process_pool6440process_pool is 2.88x faster than zoo565.92 microseconds
thread_pool6440thread_pool is 6.33x faster than zoo565.92 microseconds
apscheduler64300apscheduler is 1.66x faster than zoo842.59 microseconds
asyncio_pool64300asyncio_pool is 2.27x faster than zoo842.59 microseconds
bare_thread64300bare_thread is 1.98x faster than zoo842.59 microseconds
process_pool64300process_pool is 2.80x faster than zoo842.59 microseconds
thread_pool64300thread_pool is 2.37x faster than zoo842.59 microseconds
apscheduler642700apscheduler is 1.13x faster than zoo3.323 milliseconds
asyncio_pool642700asyncio_pool is 1.17x faster than zoo3.323 milliseconds
bare_thread642700bare_thread is 1.20x faster than zoo3.323 milliseconds
process_pool642700process_pool is 2.45x faster than zoo3.323 milliseconds
thread_pool642700thread_pool is 1.19x faster than zoo3.323 milliseconds

data: Openly published unfavourable data

Openly published unfavourable data

Openly published unfavourable data (cont.)

baselineconcurrencybody tier (microseconds)gapmedian of the framework under test
asyncio_pool6410000asyncio_pool is 1.04x faster than zoo10.636 milliseconds
bare_thread6410000bare_thread is 1.12x faster than zoo10.636 milliseconds
process_pool6410000process_pool is 2.30x faster than zoo10.636 milliseconds
thread_pool6410000thread_pool is 1.03x faster than zoo10.636 milliseconds

data: Openly published unfavourable data

zoo-bench report

tiers with no winner

Tiers where the two sides cannot be told apart: the difference is smaller than that tier's band. The band comes from the two sides' own across-round spread (stdev / median of the per-task medians) within the same run, so it varies with the sample and the reader can recompute it from the archived per-round samples. It also covers every measured tier and states a reason for each - this section is not a place to hide things: reporting noise-level differences as '1.00x faster' is.

criterion: band = the framework's own across-round relative spread plus the baseline's; a single side's across-round relative spread = stdev / median of that unit's per-round per-task medians

band this run: 1.6% to 41.9% (it varies with each tier's spread).

data: tiers with no winner

tiers with no winner

tiers with no winner (cont.)

baselineconcurrencybody tier (microseconds)ratioreason
process_pool127000.92xthe two sides differ by 7.6%, less than the band of 21.2% - no winner
apscheduler427000.91xthe two sides differ by 9.2%, less than the band of 9.6% - no winner
thread_pool4100000.92xthe two sides differ by 7.8%, less than the band of 9.2% - no winner
apscheduler16100000.98xthe two sides differ by 2.5%, less than the band of 5.1% - no winner
apscheduler64100001.00xthe two sides differ by 0.5%, less than the band of 4.1% - no winner

data: tiers with no winner

zoo-bench report

caliber limitations and sources of bias

  • measured layer: dispatch layer: drives BaseWaiter.execute_service() directly (thread-pool mode) instead of Master's full run loop - the latter's service loop fires once per second, which would lift the latency scale from microseconds to seconds. One scheduling round per submitted task, so that whole round's bookkeeping is charged to this single task; Master's usage serves several workers per round, which would spread that cost over tasks

data: caliber limitations and sources of bias

caliber limitations and sources of bias

caliber limitations and sources of bias (cont.)

  • scope note (apscheduler): APScheduler's scheduled-job model, including job registration and trigger-decision cost; the library swallows exceptions raised inside jobs, so this tier re-raises them during drain. Caliber note: the library offers no job-completion notification, so the completion signal comes from this tier's Condition, and the cost of one notification per task counts toward its end-to-end
  • scope note (asyncio_pool): the asyncio event loop dispatches synchronous bodies (equivalent to loop.run_in_executor), not native coroutine concurrency; the difference from a thread pool is the event loop's own cost

data: caliber limitations and sources of bias

caliber limitations and sources of bias

the rest of this section's details are in the full report

For one conclusion per slide, only the first 2 slides of this section are shown; the remaining 5 slides of per-unit numbers, percentiles and raw samples sit in the same section of the full report, verbatim checkable.

full report: the "caliber limitations and sources of bias" section of report.md in the same directory

zoo-bench report

bring your own baseline

the baselines of this harness are open: implement the four methods of zoo_bench.adapters.BaseAdapter (setup / submit / drain / teardown), register with zoo_bench.adapters.registry.register, or list your module path in extra_modules of matrix.yaml - no file in this repository needs to change. Your implementation goes through the same equivalence verification (submit N bodies, assert all ran and each exactly once);

failing it means the data is not trusted. We deliberately write this path into the report: a report written by the framework's own maintainer, measuring that framework, gives every reason to suspect a slowed baseline or a chosen caliber; letting anyone submit their own baseline is what makes this report challengeable and falsifiable.

data: bring your own baseline

zoo-bench report

self-check

  • overall measurement self-check: passed
  • process isolation: {'parent_pid': 2921, 'child_pids': [3381, 3384, 3387, 3391, 3394, 3400, 3406, 3412, 3418, 3436, 3454, 3472, 3490, 3546, 3603, 3661, 3729, 3731, 3733, 3735, 3737, 3742, 3747, 3752, 3758, 3775, 3792, 3809, 3828, 3887, 3947, 4010, 4079, 4113, 4147, 4181, 4215, 4348, 4482, 4615, 4748, 5277, 5806, 6335, 6865, 8979, 11092, 13205, 15321, 15325, 15329, 15333, 15337, 15344, 15351, 15358, 15365, 15385, 15404, 15423, 15442, 15509, 15576, 15643, 15712, 15714, 15716, 15718, 15720, 15725, 15730, 15735, 15744, 15761, 15778, 15796, 15813, 15869, 15926, 16009, 16084, 16086, 16088, 16090, 16092, 16097, 16102, 16107, 16112, 16129, 1
  • 6146, 16163, 16182, 16247, 16312, 16378], 'all_distinct': True, 'none_equals_parent': True}

data: self-check

appendix

every slide can be checked verbatim in the full report

slides are compressed for one conclusion per page; only the layout is compressed, never the numbers.

  • per-unit details (percentiles, spread, raw samples per round): report.md in the same directory
  • figures (SVG and PNG): figures/ in the same directory; also report.pdf
  • raw archive: results/zoo-framework-0.6.0/20261011T091515Z.json
  • 96 valid units in this run, 0 failed

the details run over a hundred rows; squeezing them into slides would strip readers of the means to verify.

← / → · space · R reset