maxAsync.pas provides:
SimpleAsyncCall(...)/iAsyncTAsyncLoopTAsyncCollectionProcessor<T>TAsyncTimercompatibility wrapper
TAsyncTimer is legacy compatibility only. For new timer work, use MaxLogic.PortableTimer.TPortableTimer.
- Reuses long-lived worker threads (
TThreadData) instead of one thread per call. - Uses global waiting-pool reuse and a main-thread local cache fast path for lower create/destroy churn.
- Keeps
WakeUp(...)path for reuse-heavy scenarios (iAsync.WakeUp).
- Executes through RTL
TParallel.For. - Caches custom
TThreadPoolinstances per requested thread count, avoiding repeated pool setup overhead.
- Uses persistent workers started once and reused.
- Supports queue modes:
acpqmLegacyLockedQueueacpqmLockedRingQueueacpqmLockFreeMpmcRingQueue
- Uses atomic pending/completion signaling semantics that keep
WaitForandOnFinishedbehavior stable. - For
acpqmLegacyLockedQueue, effective worker count is capped at2to reduce lock contention on the central queue critical section.
Command:
cd tests
./build-delphi.sh ../benchmarks/MaxLogic.Async.Benchmark.dproj
../benchmarks/Win32/MaxLogic.Async.Benchmark.exe --stable --warmup=1 --repeats=5Benchmark runner options for stabilization:
--stable: applies high process priority, highest main-thread priority, and a pinned affinity mask (default first 8 logical CPUs).--process-priority=<normal|above-normal|high|realtime>: override process priority.--main-thread-priority=<idle|lowest|lower|normal|higher|highest|time-critical>: override benchmark main thread priority.--affinity=<hex-mask|decimal-mask>: override process affinity mask (for example--affinity=0xFF).TAsyncLoopandTParallel.Forbenchmark paths now pre-warm before timing and use a longer loop workload (2,000,000iterations) to reduce short-run jitter.
Artifacts now include a stabilization section (applied controls) and a variance section for key metrics (simple_async_call, async_loop, async_collection_processor).
The benchmark output also emits warnings when RSD is above reliability thresholds (simple_async_call > 5%, async_loop > 8%, async_collection > 8%).
- Baseline reference: commit
19b8a04(before latest architecture/perf optimizations) - Current reference: three fresh benchmark invocations on
2026-03-03, each median-of-5; values below use median across those three invocations.
| Metric | Baseline ops/s | Current ops/s | Delta |
|---|---|---|---|
simple_async_call_ops_per_sec |
59,476 | 57,464 | -3.4% |
async_collection_processor_ops_per_sec |
280,554 | 633,672 | +125.9% |
async_collection_processor_batched_ops_per_sec |
1,942,493 | 2,783,197 | +43.3% |
async_collection_processor_multi_producer_ops_per_sec |
468,392 | 1,526,465 | +225.9% |
From the same three fresh runs (2026-03-03):
TAsyncCollectionProcessorvsTTask + TThreadedQueue:633,672vs209,318ops/s (~3.03xfaster)TAsyncLoop.RunAndWaitvsTParallel.For:57,571,629vs58,255,821ops/s (~98.8%of RTL)SimpleAsyncCallvsTTask.Run(sequential wait):57,464vs147,408ops/s (~39.0%of RTL)SimpleAsyncCall.WakeUpReusevsTTask.Run:72,114vs147,408ops/s (~48.9%of RTL)
TAsyncCollectionProcessor<T>is now materially faster than theTTask + TThreadedQueuebaseline for this workload.TAsyncLoopis near RTLTParallel.Forthroughput.SimpleAsyncCallstill trades raw one-shot throughput for richer reuse-oriented semantics; it performs best when reused throughWakeUp(...).