diff --git a/CHANGELOG.md b/CHANGELOG.md
index 07eb1ce..8f7dfd6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,20 @@ public class SelectionBenchmarks : RevitApiUiBenchmark
UI benchmarks use the `RevitAPIUI` types, add the `Nice3point.Revit.Api.RevitAPIUI` package reference.
+## DotTrace profiler
+
+Benchmarks support profiling with dotTrace.
+Add the `DotTraceDiagnoser` from the `BenchmarkDotNet.Diagnostics.dotTrace` package to the configuration:
+
+```csharp
+var configuration = ManualConfig.Create()
+ .AddJob(Job.Default.WithCurrentConfiguration())
+ .AddDiagnoser(new DotTraceDiagnoser());
+```
+
+The diagnoser profiles `RevitApiBenchmark` and `RevitApiUiBenchmark` classes alike.
+To profile a single class, mark it with the `[DotTraceDiagnoser]` attribute.
+
## Bug fixes
- A benchmark project that references `RevitAPIUI` runs without `FileNotFoundException` https://github.com/Nice3point/RevitBenchmark/issues/57.
diff --git a/Directory.Packages.props b/Directory.Packages.props
index c4410a3..6437ab5 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -25,6 +25,7 @@
+
diff --git a/README.md b/README.md
index cd19417..fbe9742 100644
--- a/README.md
+++ b/README.md
@@ -203,6 +203,28 @@ public class RevitSelectionBenchmarks : RevitApiUiBenchmark
UI benchmarks share one Revit instance, which starts with the first UI benchmark and closes when the run ends.
Asynchronous benchmark methods and the job settings of the benchmark process, such as the runtime and the GC mode, are not supported.
+## DotTrace profiler
+
+Profile your benchmarks with dotTrace by adding the `DotTraceDiagnoser` from the `BenchmarkDotNet.Diagnostics.dotTrace` package to the configuration:
+
+```csharp
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Diagnostics.dotTrace;
+using BenchmarkDotNet.Jobs;
+using BenchmarkDotNet.Running;
+using Nice3point.BenchmarkDotNet.Revit;
+
+var configuration = ManualConfig.Create()
+ .AddJob(Job.Default.WithCurrentConfiguration())
+ .AddDiagnoser(new DotTraceDiagnoser());
+
+BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly)
+ .Run(args, configuration);
+```
+
+The diagnoser profiles `RevitApiBenchmark` and `RevitApiUiBenchmark` classes alike.
+To profile a single class, mark it with the `[DotTraceDiagnoser]` attribute.
+
## Benchmark configuration
BenchmarkDotNet initializes Revit with the `English - United States` language, the `C:\Program Files\Autodesk\Revit {version}` installation path, and an add-in with a random ID. To override these defaults:
diff --git a/tests/Nice3point.BenchmarkDotNet.Revit.Benchmarks/Nice3point.BenchmarkDotNet.Revit.Benchmarks.csproj b/tests/Nice3point.BenchmarkDotNet.Revit.Benchmarks/Nice3point.BenchmarkDotNet.Revit.Benchmarks.csproj
index 1ddd189..082324a 100644
--- a/tests/Nice3point.BenchmarkDotNet.Revit.Benchmarks/Nice3point.BenchmarkDotNet.Revit.Benchmarks.csproj
+++ b/tests/Nice3point.BenchmarkDotNet.Revit.Benchmarks/Nice3point.BenchmarkDotNet.Revit.Benchmarks.csproj
@@ -10,6 +10,7 @@
+
diff --git a/tests/Nice3point.BenchmarkDotNet.Revit.Benchmarks/TransactionBenchmarks.cs b/tests/Nice3point.BenchmarkDotNet.Revit.Benchmarks/TransactionBenchmarks.cs
new file mode 100644
index 0000000..d348fe3
--- /dev/null
+++ b/tests/Nice3point.BenchmarkDotNet.Revit.Benchmarks/TransactionBenchmarks.cs
@@ -0,0 +1,95 @@
+using System.Globalization;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Diagnostics.dotTrace;
+
+namespace Nice3point.BenchmarkDotNet.Revit.Benchmarks;
+
+// BenchmarkDotNet v0.15.8, Windows 11 (10.0.28000.2704/26H1/2026Update)
+// AMD Ryzen 9 9950X3D 4.30GHz, 1 CPU, 32 logical and 16 physical cores
+// .NET SDK 10.0.112
+// [Host] : .NET 10.0.13 (10.0.13, 10.0.1326.50504), X64 RyuJIT x86-64-v4
+// Job-YLFTCN : .NET 10.0.13 (10.0.13, 10.0.1326.50504), X64 RyuJIT x86-64-v4
+//
+// BuildConfiguration=Release.R27 Toolchain=RevitUi
+//
+// | Method | Mean | Error | StdDev | Allocated |
+// |-------------------------------- |----------:|---------:|---------:|-----------:|
+// | SetCommentsInOneTransaction | 48.24 ms | 0.951 ms | 0.934 ms | 72.34 KB |
+// | SetCommentsInTransactionPerWall | 332.45 ms | 6.470 ms | 7.451 ms | 1678.73 KB |
+
+[DotTraceDiagnoser]
+[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
+public class TransactionBenchmarks : RevitApiUiBenchmark
+{
+ private Document _document = null!;
+ private List _walls = null!;
+ private int _revision;
+
+ protected sealed override void OnGlobalSetup()
+ {
+ var modelPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.rvt");
+ var document = UiApplication.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 < 100; i++)
+ {
+ Wall.Create(document, Line.CreateBound(new XYZ(i, 0, 0), new XYZ(i + 1, 0, 0)), level.Id, false);
+ }
+
+ transaction.Commit();
+ }
+
+ document.SaveAs(modelPath);
+ document.Close(false);
+
+ _document = UiApplication.OpenAndActivateDocument(modelPath).Document;
+ _walls = new FilteredElementCollector(_document)
+ .OfClass(typeof(Wall))
+ .Cast()
+ .ToList();
+ }
+
+ protected sealed override void OnGlobalCleanup()
+ {
+ _document.Save();
+ }
+
+ [Benchmark]
+ public void SetCommentsInOneTransaction()
+ {
+ var comment = CreateComment();
+
+ using var transaction = new Transaction(_document, "Set comments");
+ transaction.Start();
+
+ foreach (var wall in _walls)
+ {
+ wall.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS).Set(comment);
+ }
+
+ transaction.Commit();
+ }
+
+ [Benchmark]
+ public void SetCommentsInTransactionPerWall()
+ {
+ var comment = CreateComment();
+
+ foreach (var wall in _walls)
+ {
+ using var transaction = new Transaction(_document, "Set comment");
+ transaction.Start();
+ wall.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS).Set(comment);
+ transaction.Commit();
+ }
+ }
+
+ private string CreateComment()
+ {
+ return (++_revision).ToString(CultureInfo.InvariantCulture);
+ }
+}