File: SchedulerBenchmark.cs
Web Access
Project: src\src\Servers\Kestrel\perf\Microbenchmarks\Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj (Microsoft.AspNetCore.Server.Kestrel.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;
using System.IO.Pipelines;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks;
 
public class SchedulerBenchmark
{
    private const int InnerLoopCount = 1024;
    private const int OuterLoopCount = 64;
    private const int OperationsPerInvoke = InnerLoopCount * OuterLoopCount;
 
    private static readonly int IOQueueCount = Math.Min(Environment.ProcessorCount, 16);
 
    private PipeScheduler[] _ioQueueSchedulers;
    private PipeScheduler[] _threadPoolSchedulers;
    private PipeScheduler[] _inlineSchedulers;
 
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(0);
    private int _totalToReport;
    private readonly PaddedInteger[] _counters = new PaddedInteger[OuterLoopCount];
 
    private Func<int, ParallelLoopState, PipeScheduler[], PipeScheduler[]> _parallelAction;
    private Action<object> _action;
 
    [GlobalSetup]
    public void Setup()
    {
        _parallelAction = ParallelBody;
        _action = new Action<object>(ScheduledAction);
 
        _inlineSchedulers = new PipeScheduler[IOQueueCount];
        for (var i = 0; i < _inlineSchedulers.Length; i++)
        {
            _inlineSchedulers[i] = PipeScheduler.Inline;
        }
 
        _threadPoolSchedulers = new PipeScheduler[IOQueueCount];
        for (var i = 0; i < _threadPoolSchedulers.Length; i++)
        {
            _threadPoolSchedulers[i] = PipeScheduler.ThreadPool;
        }
 
        _ioQueueSchedulers = new PipeScheduler[IOQueueCount];
        for (var i = 0; i < _ioQueueSchedulers.Length; i++)
        {
            _ioQueueSchedulers[i] = new IOQueue();
        }
    }
 
    [IterationSetup]
    public void IterationSetup()
    {
        _totalToReport = OuterLoopCount;
 
        for (var i = 0; i < _counters.Length; i++)
        {
            _counters[i].Remaining = InnerLoopCount;
        }
    }
 
    [Benchmark(OperationsPerInvoke = OperationsPerInvoke, Baseline = true)]
    public void ThreadPoolScheduler() => Schedule(_threadPoolSchedulers);
 
    [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
    public void IOQueueScheduler() => Schedule(_ioQueueSchedulers);
 
    [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
    public void InlineScheduler() => Schedule(_inlineSchedulers);
 
    private void Schedule(PipeScheduler[] schedulers)
    {
        Parallel.For(0, OuterLoopCount, () => schedulers, _parallelAction, (s) => { });
 
        while (_totalToReport > 0)
        {
            _semaphore.Wait();
            _totalToReport--;
        }
    }
 
    private void ScheduledAction(object o)
    {
        var counter = (int)o;
        var result = Interlocked.Decrement(ref _counters[counter].Remaining);
        if (result == 0)
        {
            _semaphore.Release();
        }
    }
 
    private PipeScheduler[] ParallelBody(int i, ParallelLoopState state, PipeScheduler[] schedulers)
    {
        PipeScheduler pipeScheduler = schedulers[i % schedulers.Length];
        object counter = i;
        for (var t = 0; t < InnerLoopCount; t++)
        {
            pipeScheduler.Schedule(_action, counter);
        }
 
        return schedulers;
    }
 
    [StructLayout(LayoutKind.Explicit, Size = 128)]
    private struct PaddedInteger
    {
        // Padded to avoid false sharing
        [FieldOffset(64)]
        public int Remaining;
    }
}