-
Notifications
You must be signed in to change notification settings - Fork 237
Description
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:
sonar-dotnet/analyzers/rspec/cs/S6608.html
Line 174 in c8fc28e
| 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;
}