dotnet / BenchmarkDotNet

Powerful .NET library for benchmarking
https://benchmarkdotnet.org
MIT License
10.41k stars 957 forks source link

Support Parameterized Benchmark Constructors #2482

Open smfields opened 9 months ago

smfields commented 9 months ago

I'd like to be able to take a parameter that contains multiple values and display it in the summary table spread across multiple columns, rather than having all the values in the same column. For example, take this benchmark:

public record MyParamType(string Value1, int Value2);

public class MyBenchmark
{
    [ParamsSource(nameof(MyParamValues))]
    public MyParamType MyParameter { get; set; }

    public IEnumerable<MyParamType> MyParamValues()
    {
        yield return new MyParamType("first", 1);
        yield return new MyParamType("second", 2);
        yield return new MyParamType("third", 3);
    }

    [Benchmark]
    public void Benchmark() 
    {
    }
}
Rather than having it display like this (the default): Method MyParameter Mean Error
Benchmark MyPar(...)= 1 } [42] 100.50 us NA
Benchmark MyPar(...)= 2 } [43] 99.20 us NA
Benchmark MyPar(...)= 3 } [42] 106.60 us NA

I'd like to have the value of MyParameter split accross multiple columns like so:

Method Value1 Value2 Mean Error
Benchmark first 1 100.50 us NA
Benchmark second 2 99.20 us NA
Benchmark third 3 106.60 us NA

Note:

Any ideas on whether this is something that's possible?

timcassell commented 9 months ago

I don't think that makes much sense. The values are tied together, so why would they have separate columns? If you just want a cleaner display, you can override ToString. See also #1634.

[Edit] From your notes, it looks like you actually want a way to specify the params combinations, rather than splitting up a param. Is that correct?

smfields commented 9 months ago

I don't think that makes much sense. The values are tied together, so why would they have separate columns? If you just want a cleaner display, you can override ToString. See also #1634.

Thanks for the quick reply. The values aren't exactly tied together in that way. It's more that only some combinations of parameters actually make sense, but not the full cartisian product of all parameters. So at the moment I'm forced to use ParamsSource and specify the combinations manually rather then using Params and letting the framework do all combinations.

From your notes, it looks like you actually want a way to specify the params combinations, rather than splitting up a param. Is that correct?

Yup, more fine grained control over how parameters are combined would also totally work, although I'm not quite sure what that would look like.

It might be helpful to give a more realistic example. This isn't exactly what I'm doing, but imagine I had these parameters:

In this case, I'd basically want the following test matrix: Repository DataStore
SQLRepository Postgres
SQLRepository SQL Server
SQLRepository MySQL
NoSQLRepository MongoDB
NoSQLRepository CosmosDB
TableStorageRepository Azure Table
TableStorageRepository Local File

The GlobalSetup is then making sure the correct repository and data store are setup. Once that's done the benchmark can then work with the common IRepository interface and use the same benchmark accross all repository and data store types.

If you can think of any other way of acheiving this that would also be helpful.

smfields commented 8 months ago

I've managed to work out something that uses inheritence to accomplish what I need. Essentially I have an abstract base class that contains some shared parameters, the Benchmark method, and the setup and teardown methods, and then I've got multiple concrete classes that extend that base class and specify additional parameters. The parameter values from the concrete subclass are combined with the parameters from the base class, but not with the parameters from other subclasses.

What would really be ideal would be a way to specify one source method that provides multiple parameter values, similar to how the ArgumentSource works, but where those values could still be accessed from setup and teardown methods. This could be done by doing something similar to NUnit's TestFixtureSource, where you specify a source and that gets turned into constructor parameters on the class.

timcassell commented 8 months ago

That's not a bad idea. We currently only support parameter-less constructors. We could add parameter support.