WPF HexEditor - Performance Benchmarks
Scientific performance measurements using BenchmarkDotNet - the industry-standard .NET benchmarking library.
π― Purpose
Provides precise, reproducible performance measurements for:
- Span<byte> Optimizations - Memory allocation comparisons
- Async/Await Operations - Throughput and responsiveness
- Search Algorithms - Pattern matching performance
π Quick Start
Run All Benchmarks
cd Sources/Benchmarks/WpfHexEditor.Benchmarks
dotnet run -c Release
Run Specific Benchmark
# Only Span benchmarks
dotnet run -c Release --filter "*SpanBenchmarks*"
# Only Async benchmarks
dotnet run -c Release --filter "*AsyncBenchmarks*"
# Only Search benchmarks
dotnet run -c Release --filter "*SearchBenchmarks*"
Interactive Menu
dotnet run -c Release
# Then select benchmark by number
π Benchmark Suites
1. SpanBenchmarks
Compares:
- β
TraditionalArrayAllocation()- Allocates arrays every iteration - β
SpanWithArrayPool()- Uses Span<byte> with pooling - β
SpanExtensionMethod()- Direct Span<byte> extension
Parameters:
- Chunk sizes: 1 KB, 8 KB, 64 KB
- Test file: 1 MB
Metrics:
- Execution time (Mean, Median, StdDev)
- Memory allocated per operation
- Gen 0/1/2 GC collections
- Threading overhead
Expected Results:
| Method | ChunkSize | Mean | Allocated |
|---------------------------- |---------- |----------:|----------:|
| TraditionalArrayAllocation | 8192 | 5.214 ms | 128.5 KB |
| SpanWithArrayPool | 8192 | 1.856 ms | 0.8 KB |
| SpanExtensionMethod | 8192 | 1.782 ms | 0.5 KB |
2. AsyncBenchmarks
Compares:
- β
SynchronousFindAll()- Blocks calling thread - β
AsynchronousFindAll()- Non-blocking with progress - β
AsynchronousWithCancellation()- Supports cancellation
Parameters:
- File sizes: 1 MB, 10 MB
- Pattern:
0x42 0x00(appears ~100 times per MB)
Metrics:
- Execution time
- Memory usage
- Threading efficiency
- Async overhead
Expected Results:
| Method | FileSize | Mean | Allocated |
|-------------------------------- |---------- |----------:|----------:|
| SynchronousFindAll | 10485760 | 142.3 ms | 4.2 MB |
| AsynchronousFindAll | 10485760 | 145.8 ms | 4.5 MB |
| AsynchronousWithCancellation | 10485760 | 146.1 ms | 4.6 MB |
Note: Async methods have slightly higher overhead (~3%) but provide UI responsiveness.
3. SearchBenchmarks
Measures:
FindFirst()- First occurrenceFindAllCount()- All occurrencesFindNext()- Next after positionFindLast()- Last occurrence
Parameters:
- Pattern sizes: 2, 4, 8, 16 bytes
- Test file: 5 MB
Metrics:
- Execution time per pattern size
- Memory allocation
- Algorithm complexity validation
Expected Results:
| Method | PatternSize | Mean | Allocated |
|--------------- |------------ |----------:|----------:|
| FindFirst | 2 | 2.145 ms | 1.2 KB |
| FindFirst | 16 | 2.089 ms | 1.2 KB |
| FindAllCount | 2 | 42.378 ms | 12.5 KB |
| FindLast | 2 | 2.198 ms | 1.2 KB |
π¬ Understanding Results
Metrics Explained
Mean: Average execution time across all iterations
Median: Middle value (less affected by outliers)
StdDev: Standard deviation (consistency measure)
Allocated: Total memory allocated during operation
Gen 0/1/2: Number of garbage collections triggered
Ratio: Performance relative to baseline (lower is better)
Reading the Output
| Method | Mean | Error | StdDev | Ratio | Allocated |
|--------------------- |---------:|---------:|---------:|------:|----------:|
| Baseline_Old | 5.214 ms | 0.102 ms | 0.095 ms | 1.00 | 128.50 KB |
| Optimized_New | 1.856 ms | 0.035 ms | 0.033 ms | 0.36 | 0.80 KB |
Interpretation:
- New method is 2.8x faster (ratio 0.36 = 36% of baseline time)
- 160x less memory allocated (0.8 KB vs 128.5 KB)
- Consistent performance (low StdDev)
π Advanced Usage
Export Results
# Export to CSV
dotnet run -c Release --exporters csv
# Export to HTML
dotnet run -c Release --exporters html
# Export to Markdown
dotnet run -c Release --exporters markdown
# All formats
dotnet run -c Release --exporters csv html markdown
Results saved to: BenchmarkDotNet.Artifacts/results/
Memory Profiling
# Enable detailed memory diagnostics
dotnet run -c Release --memory
# Enable ETW profiling (Windows only)
dotnet run -c Release --profiler ETW
Compare Configurations
# Compare Debug vs Release
dotnet run -c Release --runtimes net8.0 --job Default
# Compare with baseline commit
git checkout baseline-commit
dotnet run -c Release > baseline.txt
git checkout current-branch
dotnet run -c Release > current.txt
# Use BenchmarkDotNet.ResultsComparer to diff
π οΈ Requirements
- .NET 8.0 SDK for Windows
- Windows 10/11 (WPF dependency)
- Administrator privileges (optional, for ETW profiling)
- Release mode (Debug mode disables optimizations)
Note: This benchmark project requires net8.0-windows and UseWPF=true because ByteProvider depends on WPF (PresentationFramework). BenchmarkDotNet will run in a WPF-enabled console context.
π Adding Custom Benchmarks
1. Create Benchmark Class
using BenchmarkDotNet.Attributes;
[MemoryDiagnoser]
[SimpleJob(warmupCount: 3, iterationCount: 10)]
public class MyBenchmarks
{
[Params(100, 1000, 10000)]
public int Size { get; set; }
[GlobalSetup]
public void Setup()
{
// Initialize resources
}
[Benchmark(Baseline = true)]
public void OldMethod()
{
// Original implementation
}
[Benchmark]
public void NewMethod()
{
// Optimized implementation
}
[GlobalCleanup]
public void Cleanup()
{
// Dispose resources
}
}
2. Run New Benchmark
dotnet run -c Release --filter "*MyBenchmarks*"
π Best Practices
DO:
β
Always run in Release mode (-c Release)
β
Close other applications to reduce noise
β
Run multiple iterations for statistical significance
β
Use [MemoryDiagnoser] to track allocations
β
Use [Params] to test different input sizes
β
Set Baseline = true on the original method
DONβT:
β Run in Debug mode (skews results) β Compare results from different machines β Use Console.WriteLine in benchmarks (adds overhead) β Modify code between baseline and comparison runs β Run with heavy background processes active
π Sample Report
After running benchmarks, youβll see:
BenchmarkDotNet v0.13.12, Windows 11 (10.0.26200.0)
12th Gen Intel Core i7-12700K, 1 CPU, 12 logical and 12 physical cores
.NET SDK 8.0.100
[Host] : .NET 8.0.0, X64 RyuJIT AVX2
DefaultJob : .NET 8.0.0, X64 RyuJIT AVX2
| Method | ChunkSize | Mean | Error | Ratio | Gen0 | Allocated |
|--------------------- |---------- |----------:|---------:|------:|-------:|----------:|
| TraditionalArray | 8192 | 5.214 ms | 0.102 ms | 1.00 | 15.625 | 128.50 KB |
| SpanWithArrayPool | 8192 | 1.856 ms | 0.035 ms | 0.36 | 0.000 | 0.80 KB |
// * Legends *
Mean : Arithmetic mean of all measurements
Error : Half of 99.9% confidence interval
Ratio : Mean of the ratio distribution ([Current]/[Baseline])
Gen0 : GC Generation 0 collects per 1000 operations
Allocated : Allocated memory per single operation (managed only)
1 ms : 1 Millisecond (0.001 sec)
π Related Documentation
- BenchmarkDotNet Official Docs
- Performance Sample - Interactive demos
- Performance Extensions - API guide
π€ Contributing
Want to add more benchmarks?
- Create new benchmark class in this project
- Add
[MemoryDiagnoser]and[SimpleJob]attributes - Mark baseline method with
[Benchmark(Baseline = true)] - Update this README with expected results
- Submit pull request!
Ideas for new benchmarks:
- SIMD vectorization performance
- Memory-mapped file operations
- Parallel processing scalability
- Custom encoding conversions
π License
Apache 2.0 - Same as WPF HexEditor parent project
π¬ Measure twice, optimize once!