Benchmarks
Trax.Core adds overhead to every train invocation. This page presents honest numbers so you can make informed decisions about where it's appropriate to use.
All benchmarks use BenchmarkDotNet and are located in tests/Trax.Core.Tests.Benchmarks/.
Test Environment
BenchmarkDotNet v0.14.0, CachyOS
AMD Ryzen 7 7840U w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK 10.0.103
[Host] : .NET 10.0.3, X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
What's Being Measured
Three execution modes are compared for identical workloads:
| Mode | Description |
|---|---|
| Serial | Plain function calls — no framework, no abstractions |
| BaseTrain | Train<TIn, TOut> — the core Chain/Resolve pipeline with Either error handling |
| ServiceTrain (no effects) | ServiceTrain<TIn, TOut> — full DI-resolved train with effect runner lifecycle, but no effect providers registered |
Train Overhead
How much does Trax.Core cost for different kinds of work?
| Method | Mean | Allocated |
|---|---|---|
| Serial — Add 1 | 0.23 ns | — |
| BaseTrain — Add 1 | 1,564 ns | 3,688 B |
| ServiceTrain — Add 1 | 7,061 ns | 7,176 B |
| Serial — Add 3 (3 junctions) | 0.23 ns | — |
| BaseTrain — Add 3 | 1,966 ns | 4,536 B |
| ServiceTrain — Add 3 | 7,696 ns | 8,024 B |
| Serial — Transform (DTO → entity) | 19.7 ns | 152 B |
| BaseTrain — Transform | 1,340 ns | 1,648 B |
| ServiceTrain — Transform | 6,889 ns | 5,232 B |
Serial — Simulated I/O (3× Task.Yield) | 1,053 ns | 112 B |
| BaseTrain — Simulated I/O | 3,747 ns | 4,992 B |
| ServiceTrain — Simulated I/O | 9,516 ns | 8,479 B |
Reading the Numbers
For trivial arithmetic, the framework overhead looks enormous in relative terms — a BaseTrain is ~6,900× slower than input + 1. But that comparison is misleading because input + 1 completes in a fraction of a nanosecond.
In absolute terms:
- BaseTrain adds roughly 1.5 μs of fixed overhead per invocation.
- ServiceTrain (no effects) adds roughly 7 μs per invocation, covering DI scope creation and effect runner lifecycle.
Once the junctions do real work, the overhead shrinks dramatically. With simulated I/O (Task.Yield), the BaseTrain is only 3.6× the serial cost instead of 6,900×.
For a train junction that makes a database call (~1–10 ms) or an HTTP request (~50–500 ms), the 1.5–7 μs framework overhead is less than 0.01% of total execution time.
Scaling with Junction Count
How does overhead grow as you chain more junctions?
| Junctions | Serial | BaseTrain | ServiceTrain | Base Overhead/Junction | Effect Overhead/Junction |
|---|---|---|---|---|---|
| 1 | 4.7 ns | 1,630 ns | 7,186 ns | — | — |
| 3 | 4.7 ns | 1,987 ns | 7,622 ns | ~179 ns | ~218 ns |
| 5 | 5.1 ns | 2,468 ns | 8,079 ns | ~210 ns | ~223 ns |
| 10 | 10.7 ns | 3,440 ns | 9,016 ns | ~201 ns | ~203 ns |
Each additional junction adds roughly 200 ns of overhead in both train modes. This covers junction instantiation, type mapping, and Either propagation through the chain.
Memory Scaling
| Junctions | BaseTrain | ServiceTrain |
|---|---|---|
| 1 | 3,688 B | 7,176 B |
| 3 | 4,536 B | 8,024 B |
| 5 | 5,384 B | 8,872 B |
| 10 | 7,720 B | 11,352 B |
Each additional junction allocates roughly ~424 B (BaseTrain) or ~464 B (ServiceTrain).
Where the Overhead Comes From
| Source | Approximate Cost |
|---|---|
Train base class instantiation + Either wrapping | ~1.3 μs |
Per-junction: type resolution, Chain<T> dispatch, Either bind | ~200 ns/junction |
DI scope creation (CreateScope) | ~1 μs |
| Effect runner lifecycle (initialize + save, no providers) | ~4.5 μs |
Guidance
Trax.Core is not designed for hot-path, sub-microsecond operations. It's designed for business train orchestration where each junction does meaningful work — database queries, API calls, file I/O, domain logic.
Use Trax.Core when:
- Junctions perform I/O or non-trivial computation (the ~7 μs overhead is noise)
- You value error propagation, observability, and composability over raw throughput
- You're building trains that run at request-level granularity (tens to hundreds per second), not tight inner loops
Don't use Trax.Core for:
- Per-element processing in large collections (use LINQ or loops)
- Anything that needs to run millions of times per second
- Pure computation where every nanosecond matters
Running the Benchmarks Yourself
cd tests/Trax.Core.Tests.Benchmarks/
# Run all benchmarks
dotnet run -c Release -- --filter '*'
# Run a specific suite
dotnet run -c Release -- --filter '*TrainOverhead*'
dotnet run -c Release -- --filter '*Scaling*'
# List available benchmarks
dotnet run -c Release -- --list flatResults will vary by hardware. The relative ratios between serial, base, and effect trains are more meaningful than the absolute numbers.