File: Benchmarks\SpanDataProtectorComparison.cs
Web Access
Project: src\src\DataProtection\benchmarks\Microsoft.AspNetCore.DataProtection.MicroBenchmarks\Microsoft.AspNetCore.DataProtection.MicroBenchmarks.csproj (Microsoft.AspNetCore.DataProtection.MicroBenchmarks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.DependencyInjection;
 
namespace Microsoft.AspNetCore.DataProtection.MicroBenchmarks.Benchmarks;
 
/*
 
    BenchmarkDotNet=v0.13.0, OS=Windows 10.0.26100
    AMD Ryzen 9 7950X3D, 1 CPU, 32 logical and 16 physical cores
    .NET SDK=10.0.100-rc.1.25420.111
      [Host]     : .NET 10.0.0 (10.0.25.42121), X64 RyuJIT
      DefaultJob : .NET 10.0.0 (10.0.25.42111), X64 RyuJIT
      Job-UEQIYD : .NET 10.0.0 (10.0.25.42111), X64 RyuJIT
 
    Server=True
 
### Numbers before improving the code flow (only span<byte> API related changes)
|                                 Method |        Job |      Toolchain | RunStrategy | PlaintextLength |     Mean |     Error |    StdDev |   Median |      Op/s |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------------------------------------- |----------- |--------------- |------------ |---------------- |---------:|----------:|----------:|---------:|----------:|-------:|------:|------:|----------:|
|    ByteArray_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |               5 | 4.097 us | 0.0598 us | 0.0530 us | 4.079 us | 244,090.2 |      - |     - |     - |     360 B |
| PooledWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |               5 | 3.886 us | 0.0211 us | 0.0176 us | 3.880 us | 257,339.0 |      - |     - |     - |     224 B |
|    RefWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |               5 | 3.844 us | 0.0507 us | 0.0423 us | 3.823 us | 260,122.2 |      - |     - |     - |     160 B |
|    ByteArray_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |               5 | 4.178 us | 0.0825 us | 0.1356 us | 4.134 us | 239,344.6 |      - |     - |     - |     360 B |
| PooledWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |               5 | 3.902 us | 0.0258 us | 0.0202 us | 3.896 us | 256,296.9 |      - |     - |     - |     224 B |
|    RefWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |               5 | 3.943 us | 0.0635 us | 0.0731 us | 3.909 us | 253,595.7 |      - |     - |     - |     160 B |
|    ByteArray_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |              50 | 4.230 us | 0.0809 us | 0.0831 us | 4.190 us | 236,415.8 | 0.0076 |     - |     - |     456 B |
| PooledWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |              50 | 4.019 us | 0.0734 us | 0.0816 us | 3.991 us | 248,798.9 |      - |     - |     - |     224 B |
|    RefWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |              50 | 4.036 us | 0.0802 us | 0.1778 us | 3.971 us | 247,794.0 |      - |     - |     - |     160 B |
|    ByteArray_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |              50 | 4.244 us | 0.0839 us | 0.0744 us | 4.208 us | 235,623.3 |      - |     - |     - |     456 B |
| PooledWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |              50 | 4.000 us | 0.0889 us | 0.2579 us | 4.005 us | 249,994.1 |      - |     - |     - |     224 B |
|    RefWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |              50 | 3.679 us | 0.0692 us | 0.0740 us | 3.654 us | 271,839.1 |      - |     - |     - |     160 B |
|    ByteArray_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |              80 | 3.862 us | 0.0741 us | 0.0728 us | 3.857 us | 258,957.3 | 0.0076 |     - |     - |     512 B |
| PooledWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |              80 | 3.725 us | 0.0743 us | 0.1242 us | 3.677 us | 268,484.2 |      - |     - |     - |     224 B |
|    RefWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |              80 | 3.727 us | 0.0745 us | 0.1539 us | 3.665 us | 268,320.4 |      - |     - |     - |     160 B |
|    ByteArray_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |              80 | 4.039 us | 0.0805 us | 0.2078 us | 3.939 us | 247,555.9 |      - |     - |     - |     512 B |
| PooledWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |              80 | 3.809 us | 0.0751 us | 0.0835 us | 3.783 us | 262,504.4 |      - |     - |     - |     224 B |
|    RefWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |              80 | 3.718 us | 0.0711 us | 0.0665 us | 3.717 us | 268,936.9 |      - |     - |     - |     160 B |
|    ByteArray_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |             100 | 4.086 us | 0.0796 us | 0.2054 us | 4.011 us | 244,726.7 | 0.0076 |     - |     - |     552 B |
| PooledWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |             100 | 3.922 us | 0.0773 us | 0.1613 us | 3.877 us | 254,955.2 | 0.0038 |     - |     - |     224 B |
|    RefWriter_ProtectUnprotectRoundtrip | DefaultJob |        Default |     Default |             100 | 3.658 us | 0.0521 us | 0.0435 us | 3.658 us | 273,341.9 |      - |     - |     - |     160 B |
|    ByteArray_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |             100 | 4.088 us | 0.0805 us | 0.1018 us | 4.046 us | 244,641.5 |      - |     - |     - |     552 B |
| PooledWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |             100 | 3.800 us | 0.0351 us | 0.0293 us | 3.805 us | 263,132.8 |      - |     - |     - |     224 B |
|    RefWriter_ProtectUnprotectRoundtrip | Job-REJWKR | .NET Core 10.0 |  Throughput |             100 | 3.797 us | 0.0735 us | 0.1031 us | 3.752 us | 263,344.2 |      - |     - |     - |     160 B |
 
### 
 
*/
 
[SimpleJob, MemoryDiagnoser]
public class SpanDataProtectorComparison
{
    private IDataProtector _dataProtector = null!;
    private ISpanDataProtector _spanDataProtector = null!;
 
    private byte[] _plaintext = null!;
 
    [Params(5, 50, 80, 100)]
    public int PlaintextLength { get; set; }
 
    [GlobalSetup]
    public void Setup()
    {
        // Setup DataProtection as in DI
        var services = new ServiceCollection();
        services.AddDataProtection();
        var serviceProvider = services.BuildServiceProvider();
 
        _dataProtector = serviceProvider.GetDataProtector("benchmark", "test");
        _spanDataProtector = (ISpanDataProtector)_dataProtector;
 
        // Setup test data for different lengths
        var random = new Random(42); // Fixed seed for consistent results
 
        _plaintext = new byte[PlaintextLength];
        random.NextBytes(_plaintext);
    }
 
    [Benchmark]
    public int ByteArray_ProtectUnprotectRoundtrip()
    {
        // Traditional approach with allocations
        var protectedData = _dataProtector.Protect(_plaintext);
        var unprotectedData = _dataProtector.Unprotect(protectedData);
        return protectedData.Length + unprotectedData.Length;
    }
 
    [Benchmark]
    public int PooledWriter_ProtectUnprotectRoundtrip()
    {
        var protectBuffer = new PooledArrayBufferWriter<byte>(initialCapacity: 255);
        var unprotectBuffer = new PooledArrayBufferWriter<byte>(initialCapacity: PlaintextLength);
        try
        {
            _spanDataProtector.Protect(_plaintext, ref protectBuffer);
            var protectedSpan = protectBuffer.WrittenSpan;
 
            _spanDataProtector.Unprotect(protectedSpan, ref unprotectBuffer);
            var unProtectedSpan = protectBuffer.WrittenSpan;
 
            return protectedSpan.Length + unProtectedSpan.Length;
        }
        finally
        {
            protectBuffer.Dispose();
            unprotectBuffer.Dispose();
        }
    }
 
    [Benchmark]
    public unsafe int RefWriter_ProtectUnprotectRoundtrip()
    {
        var protectBuffer = new RefPooledArrayBufferWriter<byte>(stackalloc byte[255]);
        var unprotectBuffer = new RefPooledArrayBufferWriter<byte>(stackalloc byte[255]);
        try
        {
            _spanDataProtector.Protect(_plaintext, ref protectBuffer);
            var protectedSpan = protectBuffer.WrittenSpan;
 
            _spanDataProtector.Unprotect(protectedSpan, ref unprotectBuffer);
            var unProtectedSpan = unprotectBuffer.WrittenSpan;
 
            return protectedSpan.Length + unProtectedSpan.Length;
        }
        finally
        {
            protectBuffer.Dispose();
            unprotectBuffer.Dispose();
        }
    }
}