Table of Contents

Performance

If you're deciding whether Trellis is "too expensive," the short answer is usually no.

Trellis adds a very small amount of CPU overhead so you can get explicit errors, composable workflows, and easier-to-review code. In real applications, database calls, HTTP calls, and serialization dominate the timeline long before Trellis does.

Tip

Measure Trellis the same way you measure LINQ: compare it to the real work around it, not to an empty method.

The practical answer

On the latest benchmark run captured in this repository, the simple happy-path comparison showed Trellis adding roughly 4-5 ns over imperative code on a fast desktop CPU. Older runs on different hardware landed in the 11-16 ns range. The exact number moves with hardware and JIT behavior, but the conclusion does not: the overhead is tiny.

Question Practical answer
Does Trellis allocate more than equivalent imperative code? Usually no on the same path.
Is the happy path fast? Yes — common Map, Bind, and Tap calls stay in the low-nanosecond range.
Is the failure path expensive? Usually no — short-circuiting keeps it predictable.
Should I optimize Trellis before I optimize I/O? Almost never.

Why the overhead usually does not matter

A few nanoseconds sounds real in a benchmark because the benchmark is isolating the framework cost. Your production code usually is not.

Operation Typical scale
Map / Bind / Tap single-digit to low double-digit ns
JSON serialization microseconds
Database query milliseconds
HTTP call milliseconds to tens of milliseconds

That means Trellis overhead is usually lost in the noise of:

  • database access
  • network latency
  • disk I/O
  • logging and serialization
  • your own business logic

A simple example

This is the kind of code the benchmarks are measuring:

using Trellis;
using Trellis.Primitives;

var output = FirstName.TryCreate("Ada")
    .Combine(EmailAddress.TryCreate("ada@example.com"))
    .Match(
        onSuccess: values => $"{values.Item1} <{values.Item2}>",
        onFailure: error => error.Detail);

The imperative equivalent is a little closer to the metal, but not by much:

using Trellis;
using Trellis.Primitives;

var firstName = FirstName.TryCreate("Ada");
var email = EmailAddress.TryCreate("ada@example.com");

string output;
if (firstName.IsSuccess && email.IsSuccess)
    output = $"{firstName.Value} <{email.Value}>";
else
{
    Error? error = null;
    if (firstName.IsFailure)
        error = firstName.Error;
    if (email.IsFailure)
        error = error is null ? email.Error : error.Combine(email.Error);

    output = error!.Detail;
}

Headline numbers from the benchmark suite

These figures come from the benchmark data checked into this repository. Treat them as directionally useful, not as universal constants.

ROP vs imperative

Method Mean Allocated
RopStyleHappy 98.32 ns 296 B
IfStyleHappy 93.86 ns 296 B
RopStyleSad 65.63 ns 336 B
IfStyleSad 75.08 ns 336 B

Core operations

Operation Representative result
Bind ~4.85 ns for a single happy-path bind
Map ~3.24 ns for a single happy-path map
Tap ~2.88 ns for a single happy-path tap
Ensure ~12.06 ns for one predicate
Combine ~7.27 ns for two successful results
Note

Benchmarks vary by CPU, runtime version, and benchmark shape. Compare relative cost and allocation behavior more than any single absolute number.

What actually makes Trellis fast enough

1. Success-path operations are tiny

Common pipeline steps are intentionally lightweight. If your chain is mostly validation, mapping, and short-circuiting, Trellis is not likely to be your bottleneck.

2. Failure short-circuits

Once a result is failed, downstream success-path work is skipped. That makes failure behavior predictable and often surprisingly cheap.

3. The memory story is good

For equivalent logic, the benchmark suite repeatedly shows matching allocations between Trellis and imperative code. When allocations do appear, they usually come from:

  • constructing errors
  • allocating strings or objects in your mapping code
  • async machinery
  • logging or collection growth

Performance advice that actually pays off

Combine validations before you bind

When validations are independent, aggregate them first.

using Trellis;
using Trellis.Primitives;

Result<string> CreateDisplayName(string first, string last, string email) =>
    FirstName.TryCreate(first)
        .Combine(LastName.TryCreate(last))
        .Combine(EmailAddress.TryCreate(email))
        .Bind((firstName, lastName, emailAddress) =>
            Result.Success($"{firstName} {lastName} <{emailAddress}>"));

That is usually clearer and more efficient than deeply nested sequential validation.

Reuse stable errors on hot paths

using Trellis;

static class CustomerRules
{
    private static readonly Error MinimumSpendError =
        Error.Validation("Customer must have spent at least 1000.");

    public static Result<decimal> Validate(decimal totalSpend) =>
        totalSpend >= 1000m
            ? Result.Success(totalSpend)
            : MinimumSpendError;
}

Prefer ValueTask only when it is genuinely helpful

If an async API frequently completes synchronously — for example, cache hits — ValueTask can help reduce allocations. If the work is always real I/O, Task is usually fine.

Optimize I/O before you optimize Trellis

If a request is slow, look for:

  • N+1 database queries
  • repeated HTTP calls
  • unnecessary serialization
  • chatty logging
  • inefficient collection or string processing

Those costs dwarf the framework overhead.

Running the benchmarks yourself

Run the full benchmark suite:

dotnet run --project Trellis.Benchmark\Trellis.Benchmark.csproj -c Release

Run one subset:

dotnet run --project Trellis.Benchmark\Trellis.Benchmark.csproj -c Release -- --filter *Combine*

When you should care more

There are cases where micro-overhead matters:

  • extremely hot CPU-bound loops
  • high-frequency in-memory processing
  • code paths called millions of times per second
  • allocation-sensitive low-latency services

If that is your world, benchmark your exact workload. Trellis is still viable in many of those scenarios, but you should verify with production-shaped data.

Bottom line

Trellis is a trade: a few nanoseconds for clearer control flow, explicit errors, and easier composition.

For most systems, that is an excellent trade.

If you want the raw numbers, keep reading: Benchmarks.