Summing an integer array directly, with ArraySegment v using Span
Date Added (UTC):
15 Apr 2024 @ 00:24
Date Updated (UTC):15 Apr 2024 @ 00:24
.NET Version(s): Tag(s):
Added By:
.NET Developer and tech lead from Ireland!
Benchmark Results:
Benchmark Code:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Reports;
using System;
[Config(typeof(Config))]
[HideColumns(Column.Job, Column.RatioSD, Column.AllocRatio)]
[MemoryDiagnoser]
[ReturnValueValidator(failOnError: true)]
public partial class SpanBenchmark
{
private int[] data;
[Params(1000)]
public int N;
[GlobalSetup]
public void Setup()
{
data = new int[N];
var random = new Random(42);
for (int i = 0; i < N; i++)
{
data[i] = random.Next(100);
}
}
[Benchmark(Baseline = true)]
public int ArrayDirect()
{
int sum = 0;
for (int i = 0; i < data.Length; i++)
{
sum += data[i];
}
return sum;
}
[Benchmark]
public int ArraySegmentManipulation()
{
int sum = 0;
var segment = new ArraySegment<int>(data, 0, data.Length);
for (int i = 0; i < segment.Count; i++)
{
sum += segment[i];
}
return sum;
}
[Benchmark]
public int SpanManipulation()
{
int sum = 0;
Span<int> span = data.AsSpan();
for (int i = 0; i < span.Length; i++)
{
sum += span[i];
}
return sum;
}
private class Config : ManualConfig
{
public Config()
{
SummaryStyle =
SummaryStyle.Default.WithRatioStyle(RatioStyle.Trend);
}
}
}
Powered by SharpLab
// .NET 8
public int ArrayDirect()
{
int num = 0;
int num2 = 0;
while (num2 < data.Length)
{
num += data[num2];
num2++;
}
return num;
}
// .NET 8
public int ArraySegmentManipulation()
{
int num = 0;
ArraySegment<int> arraySegment = new ArraySegment<int>(data, 0, data.Length);
int num2 = 0;
while (num2 < arraySegment.Count)
{
num += arraySegment[num2];
num2++;
}
return num;
}
// .NET 8
public int SpanManipulation()
{
int num = 0;
Span<int> span = MemoryExtensions.AsSpan(data);
int num2 = 0;
while (num2 < span.Length)
{
num += span[num2];
num2++;
}
return num;
}
Powered by SharpLab
// .NET 8
.method public hidebysig
instance int32 ArrayDirect () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 28 00 00 00 01 5f 01 00 54 02 08 42 61 73
65 6c 69 6e 65 01
)
// Method begins at RVA 0x2098
// Code size 34 (0x22)
.maxstack 3
.locals init (
[0] int32 sum,
[1] int32 i
)
// sequence point: (line 43, col 9) to (line 43, col 21) in _
IL_0000: ldc.i4.0
IL_0001: stloc.0
// sequence point: (line 44, col 14) to (line 44, col 23) in _
IL_0002: ldc.i4.0
IL_0003: stloc.1
// sequence point: hidden
IL_0004: br.s IL_0015
// loop start (head: IL_0015)
// sequence point: (line 46, col 13) to (line 46, col 28) in _
IL_0006: ldloc.0
IL_0007: ldarg.0
IL_0008: ldfld int32[] SpanBenchmark::data
IL_000d: ldloc.1
IL_000e: ldelem.i4
IL_000f: add
IL_0010: stloc.0
// sequence point: (line 44, col 42) to (line 44, col 45) in _
IL_0011: ldloc.1
IL_0012: ldc.i4.1
IL_0013: add
IL_0014: stloc.1
// sequence point: (line 44, col 25) to (line 44, col 40) in _
IL_0015: ldloc.1
IL_0016: ldarg.0
IL_0017: ldfld int32[] SpanBenchmark::data
IL_001c: ldlen
IL_001d: conv.i4
IL_001e: blt.s IL_0006
// end loop
// sequence point: (line 48, col 9) to (line 48, col 20) in _
IL_0020: ldloc.0
IL_0021: ret
}
// .NET 8
.method public hidebysig
instance int32 ArraySegmentManipulation () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 33 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x20c8
// Code size 55 (0x37)
.maxstack 4
.locals init (
[0] int32 sum,
[1] valuetype [System.Runtime]System.ArraySegment`1<int32> segment,
[2] int32 i
)
// sequence point: (line 54, col 9) to (line 54, col 21) in _
IL_0000: ldc.i4.0
IL_0001: stloc.0
// sequence point: (line 55, col 9) to (line 55, col 67) in _
IL_0002: ldloca.s 1
IL_0004: ldarg.0
IL_0005: ldfld int32[] SpanBenchmark::data
IL_000a: ldc.i4.0
IL_000b: ldarg.0
IL_000c: ldfld int32[] SpanBenchmark::data
IL_0011: ldlen
IL_0012: conv.i4
IL_0013: call instance void valuetype [System.Runtime]System.ArraySegment`1<int32>::.ctor(!0[], int32, int32)
// sequence point: (line 56, col 14) to (line 56, col 23) in _
IL_0018: ldc.i4.0
IL_0019: stloc.2
// sequence point: hidden
IL_001a: br.s IL_002b
// loop start (head: IL_002b)
// sequence point: (line 58, col 13) to (line 58, col 31) in _
IL_001c: ldloc.0
IL_001d: ldloca.s 1
IL_001f: ldloc.2
IL_0020: call instance !0 valuetype [System.Runtime]System.ArraySegment`1<int32>::get_Item(int32)
IL_0025: add
IL_0026: stloc.0
// sequence point: (line 56, col 44) to (line 56, col 47) in _
IL_0027: ldloc.2
IL_0028: ldc.i4.1
IL_0029: add
IL_002a: stloc.2
// sequence point: (line 56, col 25) to (line 56, col 42) in _
IL_002b: ldloc.2
IL_002c: ldloca.s 1
IL_002e: call instance int32 valuetype [System.Runtime]System.ArraySegment`1<int32>::get_Count()
IL_0033: blt.s IL_001c
// end loop
// sequence point: (line 60, col 9) to (line 60, col 20) in _
IL_0035: ldloc.0
IL_0036: ret
}
// .NET 8
.method public hidebysig
instance int32 SpanManipulation () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 3f 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x210c
// Code size 46 (0x2e)
.maxstack 3
.locals init (
[0] int32 sum,
[1] valuetype [System.Runtime]System.Span`1<int32> span,
[2] int32 i
)
// sequence point: (line 66, col 9) to (line 66, col 21) in _
IL_0000: ldc.i4.0
IL_0001: stloc.0
// sequence point: (line 67, col 9) to (line 67, col 40) in _
IL_0002: ldarg.0
IL_0003: ldfld int32[] SpanBenchmark::data
IL_0008: call valuetype [System.Runtime]System.Span`1<!!0> [System.Memory]System.MemoryExtensions::AsSpan<int32>(!!0[])
IL_000d: stloc.1
// sequence point: (line 68, col 14) to (line 68, col 23) in _
IL_000e: ldc.i4.0
IL_000f: stloc.2
// sequence point: hidden
IL_0010: br.s IL_0022
// loop start (head: IL_0022)
// sequence point: (line 70, col 13) to (line 70, col 28) in _
IL_0012: ldloc.0
IL_0013: ldloca.s 1
IL_0015: ldloc.2
IL_0016: call instance !0& valuetype [System.Runtime]System.Span`1<int32>::get_Item(int32)
IL_001b: ldind.i4
IL_001c: add
IL_001d: stloc.0
// sequence point: (line 68, col 42) to (line 68, col 45) in _
IL_001e: ldloc.2
IL_001f: ldc.i4.1
IL_0020: add
IL_0021: stloc.2
// sequence point: (line 68, col 25) to (line 68, col 40) in _
IL_0022: ldloc.2
IL_0023: ldloca.s 1
IL_0025: call instance int32 valuetype [System.Runtime]System.Span`1<int32>::get_Length()
IL_002a: blt.s IL_0012
// end loop
// sequence point: (line 72, col 9) to (line 72, col 20) in _
IL_002c: ldloc.0
IL_002d: ret
}
Powered by SharpLab
|
|
|
Benchmark Description:
The provided benchmark code is designed to measure and compare the performance of different methods for iterating over and summing the elements of an array in C#. It uses the BenchmarkDotNet library, a powerful tool for benchmarking .NET code. The benchmarks are configured to hide certain columns (Job, RatioSD, AllocRatio) for clarity and focus on memory usage through the `MemoryDiagnoser` attribute. The `ReturnValueValidator` ensures that benchmarks return the correct value, adding a layer of correctness verification to the performance tests.
### General Setup
- **.NET Version**: Not explicitly mentioned, but the use of `Span<T>` indicates it targets .NET Core 2.1 or later, as `Span<T>` was introduced in .NET Core 2.1.
- **Configuration**: Custom configuration is defined in the `Config` class, which modifies the summary style to emphasize the trend in the ratio of performance between benchmarks.
- **Benchmark Setup**: The `GlobalSetup` method initializes an array of integers (`data`) with a fixed size (`N`, parameterized to 1000) and populates it with random numbers. This setup ensures that each benchmark method operates on the same dataset, allowing for fair comparison.
### Benchmark Methods
#### 1. ArrayDirect
- **Purpose**: This method serves as the baseline for comparison. It directly accesses elements in the array using a simple `for` loop and sums them.
- **Performance Aspect**: It measures the raw performance of array access and iteration without any additional overhead or abstraction.
- **Expected Insights**: Results from this benchmark provide a baseline for the most straightforward approach to summing array elements. Performance results here are expected to be the fastest or very close to the fastest, given the minimal overhead.
#### 2. ArraySegmentManipulation
- **Purpose**: To measure the performance impact of using `ArraySegment<T>` as a way to manipulate subsections of an array.
- **Performance Aspect**: This benchmark assesses how wrapping an array in an `ArraySegment<T>` affects iteration and access performance, including any additional overhead introduced by this abstraction.
- **Expected Insights**: Since `ArraySegment<T>` provides a layer of abstraction over the raw array, it might introduce slight overhead, resulting in slower performance compared to direct array access. However, it's important for understanding the trade-offs when using `ArraySegment<T>` for more flexible array manipulation.
#### 3. SpanManipulation
- **Purpose**: To evaluate the performance of using `Span<T>` for array manipulation, which is a newer type introduced for more efficient memory management in .NET Core.
- **Performance Aspect**: This method tests the efficiency of `Span<T>` in terms of accessing and iterating over array elements, focusing on its performance relative to direct array access and `ArraySegment<T>`.
- **Expected Insights**: `Span<T>` is designed for high-performance scenarios and provides a safe way to work with memory in .NET. The results from this benchmark are expected to be very competitive with direct array access, potentially outperforming `ArraySegment<T>` due to its design optimizations for such use cases.
### Conclusion
Running these benchmarks will provide insights into the relative performance of different methods for summing elements in an array. It highlights the trade-offs between simplicity (direct array access), flexibility (`ArraySegment<T>`), and performance with safety (`Span<T>`). Understanding these results can guide developers in choosing the most appropriate method based on their specific requirements, whether they prioritize raw performance, memory safety, or the need to manipulate subsections of arrays.