# Revit Benchmarking

> Design, write, run, or review BenchmarkDotNet measurements that run inside Autodesk Revit with Nice3point.BenchmarkDotNet.Revit. USE FOR: comparing viable Revit API implementations, measuring a Revit hot path, applying the runner's WithCurrentConfiguration requirement, and handling the OnGlobalSetup/OnGlobalCleanup document lifecycle. DO NOT USE FOR: benchmarking .NET code that does not call the Revit API.

- Skill: `nice3point/revit-benchmarking` (Agent Skill)
- Install (CLI): `npx skillmds@latest add nice3point/revit-benchmarking`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nice3point/revit-benchmarking/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: MIT
- Author: nice3point (https://skillmd.com/u/nice3point)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/nice3point/revit-benchmarking

---


# Revit Benchmarking

A Revit benchmark runs inside Revit and supplies the evidence for one production decision about a Revit API hot path.
`Nice3point.BenchmarkDotNet.Revit` marshals the benchmark onto Revit's thread; running it needs a matching licensed Revit installation.

## When to use

- Choosing between viable Revit API implementations on measured evidence.
- Measuring a Revit hot path on a representative model.

## When not to use

- The measured operation does not call the Revit API.

## Workflow

### Step 1: Write the benchmark class

Derive from `RevitApiBenchmark`.
Open the model in `OnGlobalSetup` and close it in `OnGlobalCleanup` — the base binds `[GlobalSetup]`/`[GlobalCleanup]` and calls these overrides; never add those attributes directly.
Each `[Benchmark]` holds one compared operation and returns its result; a returned result keeps the JIT from eliminating it.
Declare the alternatives as sibling `[Benchmark]` methods in the same class.

A small application-level comparison needs no document:

```csharp
public class XyzBenchmarks : RevitApiBenchmark
{
    [Benchmark]
    public XYZ Constructor()
    {
        return new XYZ(3, 4, 5);
    }

    [Benchmark]
    public XYZ Factory()
    {
        return Application.Create.NewXYZ(3, 4, 5);
    }
}
```

A benchmark that needs a seeded model opens it once in setup; keep the seeding out of the measured method:

```csharp
public class CollectorBenchmarks : RevitApiBenchmark
{
    private Document _document = null!;

    protected sealed override void OnGlobalSetup()
    {
        _document = Application.NewProjectDocument(UnitSystem.Metric);

        using var transaction = new Transaction(_document, "Seed model");
        transaction.Start();
        var level = Level.Create(_document, 0);
        for (var i = 0; i < 1000; i++)
        {
            Wall.Create(_document, Line.CreateBound(new XYZ(i, 0, 0), new XYZ(i + 1, 0, 0)), level.Id, false);
        }
        transaction.Commit();
    }

    protected sealed override void OnGlobalCleanup()
    {
        _document.Close(false);
    }

    [Benchmark]
    public IList<Element> ToElements()
    {
        return new FilteredElementCollector(_document)
            .WhereElementIsNotElementType()
            .ToElements();
    }

    [Benchmark]
    public List<Element> ToList()
    {
        return new FilteredElementCollector(_document)
            .WhereElementIsNotElementType()
            .ToList();
    }
}
```

### Step 2: Configure the runner with the current build configuration

BenchmarkDotNet builds in `Release` by default.
The build fails for Revit's multi-version configurations.
Apply `WithCurrentConfiguration()` to the job; it then builds the active `Release.RNN`.

```csharp
var configuration = ManualConfig.Create(DefaultConfig.Instance)
    .AddJob(Job.Default.WithCurrentConfiguration())
    .AddDiagnoser(MemoryDiagnoser.Default);

BenchmarkRunner.Run<CollectorBenchmarks>(configuration);
```

### Step 3: Run and decide

```shell
dotnet run -c Release.RNN
```

`RNN` is the target Revit-year configuration, for example `Release.R26`; it must match the licensed Revit installed on the machine.
Iterate with a dry run first, then measure the final comparison on a quiet machine.
Read the Markdown report; compare time, allocation, and output correctness, and record why the chosen implementation applies to production.

## Validation

- [ ] The benchmark derives from `RevitApiBenchmark`.
- [ ] The document is opened in `OnGlobalSetup` and closed in `OnGlobalCleanup`, outside the measured method.
- [ ] The runner job uses `WithCurrentConfiguration()`.
- [ ] The selected `Release.RNN` configuration matches the installed Revit runtime.
- [ ] The result drives a concrete implementation decision.

## Common Pitfalls

| Pitfall                                                                   | Correct approach                                                                                                      |
|---------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| Runner uses the default `Release` job                                     | Add `Job.Default.WithCurrentConfiguration()`.                                                                         |
| Seeding the model inside a `[Benchmark]` method                           | Do it in `OnGlobalSetup`.                                                                                             |
| `[GlobalSetup]`/`[GlobalCleanup]` added directly                          | Override `OnGlobalSetup`/`OnGlobalCleanup`; the base binds them.                                                      |
| Discovery features like `[Params]`/`[ParamsAllValues]` using a Revit type | BenchmarkDotNet reads them during discovery, before Revit initializes, and throws; use primitives or non-Revit types. |
| `RevitApiBenchmark` not found                                             | The `Nice3point.BenchmarkDotNet.Revit` package is not referenced.                                                     |

