Skip to content

tommasobertoni/TinyBenchmark

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

75 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TinyBenchmark

Package Version Downloads Samples
TinyBenchmark Nuget Nuget Samples
TinyBenchmark.Exporters Nuget Nuget Samples

GitHub last commit GitHub open issues GitHub closed issues

License

Define benchmarks with ease!

TinyBenchmark provides a simple set of APIs that enable you to inspect the execution time of different methods and compare them between each other.

Usage

Attributes

  • [Benchmark]: identifies a benchmark method
  • [Arguments]: defines the benchmark arguments
  • [WarmupWith]: identifies a warmup method to be executed before the benchmark
  • [Param]: defines all the possible values of a property that will be used by all the benchmarks
  • [Init]: identifies an initialization method to be executed before each benchmark
  • [InitWith]: identifies an initialization method to be executed before the benchmark
  • [BenchmarksContainer]: defines additional information about the class that contains the benchmarks
  • [InitContainer]: identifies an initialization method to be executed once before every benchmark

Run your first benchmark

class Demo
{
    public static void Main(string[] args)
    {
        var runner = new BenchmarkRunner();
        var report = runner.Run<BenchmarksContainer>();

        // Explore the data in the report!
        Console.WriteLine($"Total duration: {report.Duration}");
    }
}

class BenchmarksContainer
{
    [Benchmark]
    public void TestMe()
    {
        string token = "test";
        string msg = string.Empty;
        for (int i = 0; i < 10_000; i++)
            msg += token;
    }
}

Define benchmark arguments

class BenchmarksContainer
{
    /// <summary>
    /// This benchmark will be executed once for each [Arguments],
    /// with the "times" variable assigned to the value specified the attribute.
    /// </summary>
    [Benchmark]
    [Arguments(10_000)]
    [Arguments(100_000)]
    public void TestMe(int times)
    {
        string token = "test";
        string msg = string.Empty;
        for (int i = 0; i < times; i++)
            msg += token;
    }
}

Average the results over multiple iterations

class Demo
{
    public static void Main(string[] args)
    {
        var runner = new BenchmarkRunner();
        var report = runner.Run<BenchmarksContainer>();

        var benchmarkReport = report.Reports.First();
        Console.WriteLine($"Benchmark: {benchmarkReport.Name}");
        Console.WriteLine($"  average iteration duration: {benchmarkReport.AvgIterationDuration}");
    }
}

class BenchmarksContainer
{
    /// <summary>
    /// This benchmark will be executed once for each [Arguments]
    /// and once for each iteration, for a total of 6 runs.
    /// </summary>
    [Benchmark(Iterations = 3)]
    [Arguments(10_000)]
    [Arguments(100_000)]
    public void TestMe(int times)
    {
        string token = "test";
        string msg = string.Empty;
        for (int i = 0; i < times; i++)
            msg += token;
    }
}

Named benchmarks

class BenchmarksContainer
{
    [Benchmark(Name = "String concatenation")]
    public void StringConcatenation()
    {
        string msg = string.Empty;
        for (int i = 0; i < 50_000; i++)
            msg += "test";
    }
}

Benchmarks comparison

class Demo
{
    public static void Main(string[] args)
    {
        var runner = new BenchmarkRunner();
        var report = runner.Run<BenchmarksContainer>();

        var stringConcatenationBenchmarkReport = report.Reports[0];
        Console.WriteLine($"Benchmark: {stringConcatenationBenchmarkReport.Name}");
        Console.WriteLine($"  average iteration duration: {stringConcatenationBenchmarkReport.AvgIterationDuration}");

        var stringBuilderBenchmarkReport = report.Reports[1];
        Console.WriteLine($"Benchmark: {stringBuilderBenchmarkReport.Name}");
        Console.WriteLine($"  average iteration duration: {stringBuilderBenchmarkReport.AvgIterationDuration}");

        var efficiency = Math.Round(stringConcatenationBenchmarkReport.AvgIterationDuration / stringBuilderBenchmarkReport.AvgIterationDuration, 1);
        Console.WriteLine($"{stringBuilderBenchmarkReport.Name} is {efficiency} times faster than {stringConcatenationBenchmarkReport.Name}!");
    }
}

public class BenchmarksContainer
{
    private readonly string _token = "test";
    private readonly int _tokensCount = 50_000;

    [Benchmark(Name = "String concatenation")]
    public void StringConcatenation()
    {
        string msg = string.Empty;
        for (int i = 0; i < _tokensCount; i++)
            msg += _token;
    }

    [Benchmark(Name = "Concatenation using StringBuilder")]
    public void StringBuilder()
    {
        var sb = new StringBuilder();
        for (int i = 0; i < _tokensCount; i++)
            sb.Append(_token);

        var msg = sb.ToString();
    }
}

Benchmarks output

Each benchmark can write to the console using an instance of IBenchmarkOutput.
Simply declare a constructor with this parameter and the library will inject an instance of it.

Compared to simply using Console.WriteLine, this type has the advantage that is aware of the OutputLevel associated with each message, and therefore the user can easily configure how many logs should be displayed on the console.

These are the current values of OutputLevel:

  • Verbose: print everything, including the benchmarks output
  • Normal: print less information compared to Verbose, but still give enough details about the execution.
  • Minimal: print only the main steps of the execution
  • ErrorsOnly: print only if some unexpected errors occurr
  • Silent: don't print "anything"

Unless specified otherwise, the default output level of a BenchmarkRunner is Normal (therefore, by default, the benchmarks' logs won't be shown).

class Demo
{
    public static void Main(string[] args)
    {
        var runner = new BenchmarkRunner(maxOutputLevel: OutputLevel.Verbose);
        var report = runner.Run<BenchmarksContainer>();

        var benchmarkReport = report.Reports.First();
        Console.WriteLine($"Benchmark: {benchmarkReport.Name}");
        Console.WriteLine($"  average iteration duration: {benchmarkReport.AvgIterationDuration}");
    }
}

class BenchmarksContainer
{
    private readonly IBenchmarkOutput _output;

    public BenchmarksContainer(IBenchmarkOutput output)
    {
        _output = output;
    }

    [Benchmark(Name = "String concatenation")]
    public void StringConcatenation()
    {
        int times = 50_000;
        string token = "test";

        _output.WriteLine($"Concatenating {times} times the string \"{token}\"");

        string msg = string.Empty;
        for (int i = 0; i < times; i++)
            msg += token;

        _output.WriteLine("Terminated");
    }
}