PicoBench 2026.1.3
dotnet add package PicoBench --version 2026.1.3
NuGet\Install-Package PicoBench -Version 2026.1.3
<PackageReference Include="PicoBench" Version="2026.1.3" />
<PackageVersion Include="PicoBench" Version="2026.1.3" />
<PackageReference Include="PicoBench" />
paket add PicoBench --version 2026.1.3
#r "nuget: PicoBench, 2026.1.3"
#:package PicoBench@2026.1.3
#addin nuget:?package=PicoBench&version=2026.1.3
#tool nuget:?package=PicoBench&version=2026.1.3
PicoBench
English | 中文 | 中文 (Traditional) | Español | Русский | 日本語 | Français | Deutsch | Português (Brasil)
A lightweight, zero-dependency benchmarking library for .NET with two complementary APIs: an imperative fluent API and an attribute-based, source-generated API that is fully AOT-compatible.
Features
- Zero Dependencies - Pure .NET implementation, no external packages required
- Two APIs - Imperative (
Benchmark.Run) for ad-hoc tests; attribute-based ([Benchmark]+ source generator) for structured suites - AOT-Compatible Source Generator - The incremental generator emits direct method calls with zero reflection at runtime
- Cross-Platform - Full support for Windows, Linux, and macOS
- High-Precision Timing - Uses
Stopwatchwith nanosecond-level granularity - GC Tracking - Monitors Gen0/Gen1/Gen2 collection counts during benchmarks
- CPU Cycle Counting - Hardware-level cycle counting (Windows via
QueryThreadCycleTime, Linux viaperf_event, macOS viamach_absolute_time) - Statistical Analysis - Mean, Median, P90, P95, P99, Min, Max, StdDev, StdErr, and relative variance
- Multiple Output Formats - Console, Markdown, HTML, CSV and programmatic summary
- Parameterised Benchmarks -
[Params]attribute with automatic Cartesian product iteration - Comparison Support - Baseline vs candidate with speedup calculations
- Configurable - Quick, Default, and Precise presets, auto-calibration, or fully custom configuration
- netstandard2.0 - Compatible with .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+
Installation
Reference the PicoBench NuGet package. The source generator (PicoBench.Generators) is bundled automatically as an analyzer - no extra reference needed.
dotnet add package PicoBench
Quick Start
Imperative API
using PicoBench;
var result = Benchmark.Run("My Benchmark", () =>
{
Thread.SpinWait(100);
});
Console.WriteLine($"Average: {result.Statistics.Avg:F1} ns/op");
Attribute-Based API (Source-Generated)
using PicoBench;
var suite = BenchmarkRunner.Run<MyBenchmarks>();
Console.WriteLine(new PicoBench.Formatters.ConsoleFormatter().Format(suite));
[BenchmarkClass]
public partial class MyBenchmarks
{
[Benchmark(Baseline = true)]
public void Baseline() { /* ... */ }
[Benchmark]
public void Candidate() { /* ... */ }
}
The class must be
partial. The source generator emits anIBenchmarkClassimplementation at compile time - no reflection, fully AOT-safe.
Invalid attribute usage now produces generator diagnostics for common mistakes such as non-
partialclasses, duplicate baselines, invalid lifecycle signatures, and incompatible[Params]values.
Imperative API Reference
Basic Benchmark
using PicoBench;
using PicoBench.Formatters;
var result = Benchmark.Run("SpinWait", () => Thread.SpinWait(100));
Console.WriteLine(new ConsoleFormatter().Format(result));
Benchmark with State (Avoid Closures)
var data = new byte[1024];
var result = Benchmark.Run("ArrayCopy", data, static d =>
{
var copy = new byte[d.Length];
Buffer.BlockCopy(d, 0, copy, 0, d.Length);
});
Scoped Benchmarks (DI-Friendly)
var result = Benchmark.RunScoped("DbQuery",
() => new MyDbContext(),
static ctx => ctx.Users.FirstOrDefault()
);
// A new scope is created per sample; the scope is disposed after each sample.
Comparing Two Implementations
var comparison = Benchmark.Compare(
"String vs StringBuilder",
"String Concat", () => { var s = ""; for (int i = 0; i < 100; i++) s += "a"; },
"StringBuilder", () => { var sb = new StringBuilder(); for (int i = 0; i < 100; i++) sb.Append('a'); _ = sb.ToString(); }
);
Console.WriteLine($"Speedup: {comparison.Speedup:F2}x ({comparison.ImprovementPercent:F1}%)");
Advanced: Separate Warmup, Setup & Teardown
var result = Benchmark.Run(
name: "Custom",
action: () => DoWork(),
warmup: () => DoWork(), // null to skip warmup
config: BenchmarkConfig.Precise,
setup: () => PrepareState(), // called before each sample (not timed)
teardown: () => CleanUp() // called after each sample (not timed)
);
Attribute-Based API Reference
Decorate a partial class with [BenchmarkClass] and its methods/properties with the attributes below. The source generator emits all wiring code at compile time.
Attributes
| Attribute | Target | Description |
|---|---|---|
[BenchmarkClass] |
Class | Marks the class for code generation. Optional Description property. |
[Benchmark] |
Method | Marks a parameterless method as a benchmark. Set Baseline = true for the reference method. Optional Description. |
[Params(values)] |
Property / Field | Iterates the given compile-time constant values. Multiple [Params] properties produce a Cartesian product. |
[GlobalSetup] |
Method | Called once per parameter combination, before benchmarks run. |
[GlobalCleanup] |
Method | Called once per parameter combination, after benchmarks run. |
[IterationSetup] |
Method | Called before each sample (not timed). |
[IterationCleanup] |
Method | Called after each sample (not timed). |
[Benchmark] methods must be instance, non-generic, and parameterless. Lifecycle methods must be instance, non-generic, parameterless, and void. [Params] targets must be writable instance properties or non-readonly instance fields.
Full Example
using PicoBench;
[BenchmarkClass(Description = "Comparing string concatenation strategies")]
public partial class StringBenchmarks
{
[Params(10, 100, 1000)]
public int N { get; set; }
[GlobalSetup]
public void Setup() { /* prepare data for current N */ }
[GlobalCleanup]
public void Cleanup() { /* release resources */ }
[IterationSetup]
public void BeforeSample() { /* per-sample preparation */ }
[Benchmark(Baseline = true)]
public void StringConcat()
{
var s = string.Empty;
for (var i = 0; i < N; i++) s += "a";
}
[Benchmark]
public void StringBuilder()
{
var sb = new System.Text.StringBuilder();
for (var i = 0; i < N; i++) sb.Append('a');
_ = sb.ToString();
}
}
Running
// Create instance internally:
var suite = BenchmarkRunner.Run<StringBenchmarks>(BenchmarkConfig.Quick);
// Or with a pre-configured instance:
var instance = new StringBenchmarks();
var suite2 = BenchmarkRunner.Run(instance, BenchmarkConfig.Quick);
Configuration
Presets
| Preset | Warmup | Samples | Base Iters/Sample | Auto-Calibrate | Use Case |
|---|---|---|---|---|---|
Quick |
100 | 10 | 1,000 | Yes | Fast iteration / CI |
Default |
1,000 | 100 | 10,000 | No | General benchmarking |
Precise |
5,000 | 200 | 50,000 | Yes | Final measurements |
Custom Configuration
var config = new BenchmarkConfig
{
WarmupIterations = 500,
SampleCount = 50,
IterationsPerSample = 5000,
RetainSamples = true, // Keep raw TimingSample data
AutoCalibrateIterations = true,
MinSampleTime = TimeSpan.FromMilliseconds(0.5),
MaxAutoIterationsPerSample = 1_000_000
};
var result = Benchmark.Run("Test", action, config);
When auto-calibration is enabled, PicoBench increases IterationsPerSample until a minimum sample-time budget is reached or MaxAutoIterationsPerSample is hit. This is especially useful for ultra-fast operations that would otherwise be dominated by timer noise.
Output Formatters
Five built-in formatters implement IFormatter:
using PicoBench.Formatters;
var console = new ConsoleFormatter(); // Box-drawing console tables
var markdown = new MarkdownFormatter(); // GitHub-friendly Markdown
var html = new HtmlFormatter(); // Styled HTML report
var csv = new CsvFormatter(); // CSV for data analysis
// Static helper for comparison summaries:
Console.WriteLine(SummaryFormatter.Format(suite.Comparisons));
Console, Markdown, HTML, and CSV outputs now include precision-oriented metadata such as standard error, relative standard deviation, and CPU counter notes when available.
Formatting Targets
formatter.Format(result); // Single BenchmarkResult
formatter.Format(results); // IEnumerable<BenchmarkResult>
formatter.Format(comparison); // Single ComparisonResult
formatter.Format(comparisons); // IEnumerable<ComparisonResult>
formatter.Format(suite); // Complete BenchmarkSuite
Formatter Options
var options = new FormatterOptions
{
IncludeEnvironment = true,
IncludeTimestamp = true,
IncludeGcInfo = true,
IncludeCpuCycles = true,
IncludePercentiles = true,
TimeDecimalPlaces = 1,
SpeedupDecimalPlaces = 2,
BaselineLabel = "Old",
CandidateLabel = "New"
};
var formatter = new ConsoleFormatter(options);
// Also available: FormatterOptions.Default, .Compact, .Minimal
Saving Results
var dir = Path.Combine(AppContext.BaseDirectory, "results");
Directory.CreateDirectory(dir);
File.WriteAllText(Path.Combine(dir, "results.md"), new MarkdownFormatter().Format(suite));
File.WriteAllText(Path.Combine(dir, "results.html"), new HtmlFormatter().Format(suite));
File.WriteAllText(Path.Combine(dir, "results.csv"), new CsvFormatter().Format(suite));
Result Model
| Type | Description |
|---|---|
BenchmarkResult |
Name, Statistics, Samples, IterationsPerSample, SampleCount, Tags, Category |
ComparisonResult |
Baseline, Candidate, Speedup, IsFaster, ImprovementPercent |
BenchmarkSuite |
Name, Description, Results, Comparisons, Environment, Duration |
Statistics |
Avg, P50, P90, P95, P99, Min, Max, StdDev, StandardError, RelativeStdDevPercent, CpuCyclesPerOp, GcInfo |
TimingSample |
ElapsedNanoseconds, ElapsedMilliseconds, ElapsedTicks, CpuCycles, GcInfo |
GcInfo |
Gen0, Gen1, Gen2, Total, IsZero |
EnvironmentInfo |
Os, Architecture, RuntimeVersion, ProcessorCount, Configuration, CPU counter kind / availability |
Architecture
src/
+-- PicoBench/ # Main library (netstandard2.0)
| +-- Benchmark.cs # Imperative API (Run, Compare, RunScoped)
| +-- BenchmarkRunner.cs # Attribute-based entry point (Run<T>)
| +-- BenchmarkConfig.cs # Configuration with presets
| +-- Attributes.cs # 7 benchmark attributes
| +-- IBenchmarkClass.cs # Interface emitted by the generator
| +-- Runner.cs # Low-level timing engine
| +-- StatisticsCalculator.cs # Percentile / stats computation
| +-- Models.cs # Result types
| +-- Formatters/
| +-- IFormatter.cs # IFormatter, FormatterOptions & FormatterBase
| +-- ConsoleFormatter.cs # Box-drawing console tables
| +-- MarkdownFormatter.cs # GitHub Markdown tables
| +-- HtmlFormatter.cs # Styled HTML reports
| +-- CsvFormatter.cs # CSV export
| +-- SummaryFormatter.cs # Win/loss summary
|
+-- PicoBench.Generators/ # Source generator (netstandard2.0)
+-- BenchmarkGenerator.cs # IIncrementalGenerator entry point
+-- Emitter.cs # C# code emitter (AOT-safe)
+-- Models.cs # Roslyn analysis models
Platform-Specific Features
| Feature | Windows | Linux | macOS |
|---|---|---|---|
| High-precision timing | Stopwatch | Stopwatch | Stopwatch |
| GC tracking (Gen0/1/2) | Yes | Yes | Yes |
| CPU cycle counting | QueryThreadCycleTime |
perf_event_open |
mach_absolute_time |
| Process priority boost | Yes | Yes | Yes |
On macOS the exported CPU counter is a high-resolution monotonic proxy rather than architectural cycle counts. EnvironmentInfo and formatter output expose this distinction explicitly.
Samples
| Sample | API Style | Description |
|---|---|---|
StringVsStringBuilder |
Imperative | Compares string +=, StringBuilder, and StringBuilder with capacity |
AttributeBased |
Attribute | Same comparison using [Benchmark], [Params], and the source generator |
CollectionBenchmarks |
Attribute | List vs Dictionary vs HashSet lookup - showcases every attribute |
dotnet run --project samples/StringVsStringBuilder -c Release
dotnet run --project samples/AttributeBased -c Release
dotnet run --project samples/CollectionBenchmarks -c Release
Comparison with BenchmarkDotNet
| Feature | PicoBench | BenchmarkDotNet |
|---|---|---|
| Dependencies | 0 | Many |
| Package size | Tiny | Large |
| Target framework | netstandard2.0 | net6.0+ |
| AOT support | Source generator | Reflection-based |
| Attribute API | [Benchmark], [Params] |
[Benchmark], [Params] |
| Setup time | Instant | Seconds |
| Output formats | 5 | 10+ |
| Statistical depth | Good | Extensive |
| Use case | Quick A/B tests, CI, AOT apps | Detailed analysis, publications |
License
MIT License - see LICENSE file for details.
Building and Publishing
dotnet build --configuration Release
dotnet test --configuration Release
dotnet pack src/PicoBench/PicoBench.csproj --configuration Release --include-symbols --output ./nupkg
Releases are tag-driven — push a version tag (e.g. git tag v2026.2.0 && git push origin v2026.2.0) and the GitHub Actions pipeline will test, pack, and publish to NuGet.org automatically.
Contributing
- Fork the repository
- Create a feature branch
- Make changes with tests
- Submit a pull request
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.