Skip to content

Rule S6608: Benchmark is benchmarking the wrong things #9390

@Apollo3zehn

Description

@Apollo3zehn

I got a S6608 warning using SolarLink in VSCode and ran the benchmark referenced in the documentation.

The result is:

Method SampleSize LoopSize Mean Error StdDev
ElementAt 1000000 1000 11,614.7 ns 199.34 ns 204.71 ns
Index 1000000 1000 7,756.9 ns 54.88 ns 45.83 ns
First 1000000 1000 6,223.9 ns 23.47 ns 19.60 ns
First_Index 1000000 1000 483.3 ns 9.31 ns 14.50 ns
Last 1000000 1000 6,483.0 ns 128.93 ns 188.98 ns
Last_Index 1000000 1000 380.2 ns 7.19 ns 7.06 ns

The results suggest that ElementAt and Index are slow methods but the actual problem is that on every loop iteration one random value is being generated which is much slower than the actual array access:

var index = random.Next(0, SampleSize);

A simple benchmark tells me that random.Next(0, SampleSize) itself takes around 7000 ns so it has a huge impact on the actual benchmark results. Without random.Next(0, SampleSize) the method ElementAt becomes much less slow and Index is on par with First_Index and Last_Index.

Method SampleSize LoopSize Mean Error StdDev
ElementAt 1000000 1000 3,450.9 ns 68.35 ns 104.38 ns
Index 1000000 1000 530.0 ns 9.63 ns 8.54 ns
First 1000000 1000 6,739.5 ns 134.67 ns 209.66 ns
First_Index 1000000 1000 528.3 ns 3.57 ns 2.78 ns
Last 1000000 1000 6,803.1 ns 44.47 ns 39.42 ns
Last_Index 1000000 1000 459.6 ns 1.66 ns 1.30 ns

The modified code is shown below. The only disadvantage of not using a random index is that now the CPU cache is being used in an optimal way which makes the result look a bit better than for a real random access.

To avoid the risk of code being optimized out I added a return value to all benchmark methods (https://fransbouma.github.io/BenchmarkDotNet/RulesOfBenchmarking.htm#avoid-dead-code-elimination).

private List<byte> data;
private Random random;

[Params(1_000_000)]
public int SampleSize;

[Params(1_000)]
public int LoopSize;

[GlobalSetup]
public void Setup()
{
    random = new Random(42);
    var bytes = new byte[SampleSize];
    random.NextBytes(bytes);
    data = bytes.ToList();
}

[Benchmark]
public int ElementAt()
{
    int result = default;

    for (int i = 0; i < LoopSize; i++)
    {
        result = random.Next(0, SampleSize);
    }

    return result;
}

[Benchmark]
public int Index()
{
    int result = default;

    for (int i = 0; i < LoopSize; i++)
    {
        result = data[i];
    }

    return result;
}

[Benchmark]
public int RandomNext()
{
    int result = default;

    for (int i = 0; i < LoopSize; i++)
    {
        result = data.First();
    }

    return result;
}

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions