Skip to content

Instantly share code, notes, and snippets.

@Stroniax
Created February 16, 2023 19:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Stroniax/145d2e87836604a40fc40195872245b9 to your computer and use it in GitHub Desktop.
Save Stroniax/145d2e87836604a40fc40195872245b9 to your computer and use it in GitHub Desktop.
SingleCollection Benchmark
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SingleCollectionBenchmark;
/// <summary>
/// <code>
/// BenchmarkDotNet=v0.13.4, OS=Windows 10 (10.0.19044.2486/21H2/November2021Update)
/// Intel Core i5-7500 CPU 3.40GHz(Kaby Lake), 1 CPU, 4 logical and 4 physical cores
/// .NET SDK= 7.0.102
/// [Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2[AttachedDebugger]
/// DefaultJob : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
///
/// | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
/// |------------------------------------------------- |---------:|---------:|---------:|------:|--------:|-------:|----------:|------------:|
/// | Array | 21.27 ns | 0.100 ns | 0.088 ns | 1.00 | 0.00 | 0.0204 | 64 B | 1.00 |
/// | List | 43.40 ns | 0.883 ns | 1.084 ns | 2.03 | 0.05 | 0.0408 | 128 B | 2.00 |
/// | Yield | 19.37 ns | 0.110 ns | 0.086 ns | 0.91 | 0.01 | 0.0153 | 48 B | 0.75 |
/// | SingleCollection | 17.41 ns | 0.250 ns | 0.222 ns | 0.82 | 0.01 | 0.0179 | 56 B | 0.88 |
/// | SingleCollectionAndEnumerator | 14.04 ns | 0.224 ns | 0.198 ns | 0.66 | 0.01 | 0.0102 | 32 B | 0.50 |
/// | SingleCollectionValueEnumerator | 24.98 ns | 0.128 ns | 0.107 ns | 1.18 | 0.01 | 0.0179 | 56 B | 0.88 |
/// | ValueSingleCollection | 27.51 ns | 0.579 ns | 0.541 ns | 1.29 | 0.03 | 0.0179 | 56 B | 0.88 |
/// | ValueSingleCollectionReferenceEnumerator | 19.34 ns | 0.186 ns | 0.156 ns | 0.91 | 0.01 | 0.0179 | 56 B | 0.88 |
/// | ReadonlyValueSingleCollection | 25.99 ns | 0.070 ns | 0.062 ns | 1.22 | 0.01 | 0.0179 | 56 B | 0.88 |
/// | ReadonlyValueSingleCollectionReferenceEnumerator | 20.31 ns | 0.410 ns | 0.403 ns | 0.95 | 0.02 | 0.0179 | 56 B | 0.88 |
/// </code>
/// </summary>
[MemoryDiagnoser]
public class Program
{
public static void Main()
{
BenchmarkRunner.Run<Program>();
}
[Benchmark(Baseline = true)]
public double Array() => Calculation(FromArray(1.23));
[Benchmark]
public double List() => Calculation(FromList(1.23));
[Benchmark]
public double Yield() => Calculation(FromYield(1.23));
[Benchmark]
public double SingleCollection() => Calculation(FromSingleCollection(1.23));
[Benchmark]
public double SingleCollectionAndEnumerator() => Calculation(FromSingleCollectionAndEnumerator(1.23));
[Benchmark]
public double SingleCollectionValueEnumerator() => Calculation(FromSingleCollectionValueEnumerator(1.23));
[Benchmark]
public double ValueSingleCollection() => Calculation(FromValueSingleCollection(1.23));
[Benchmark]
public double ValueSingleCollectionReferenceEnumerator() => Calculation(FromValueSingleCollectionReferenceEnumerator(1.23));
[Benchmark]
public double ReadonlyValueSingleCollection() => Calculation(FromReadonlyValueSingleCollection(1.23));
[Benchmark]
public double ReadonlyValueSingleCollectionReferenceEnumerator() => Calculation(FromReadonlyValueSingleCollectionReferenceEnumerator(1.23));
private static double Calculation(IEnumerable<double> values)
{
var result = 0.0;
foreach (var value in values)
{
result += value * value;
}
return result;
}
private static IEnumerable<T> FromArray<T>(T item) => new T[] { item };
private static IEnumerable<T> FromList<T>(T item) => new List<T>() { item };
private static IEnumerable<T> FromYield<T>(T item)
{
yield return item;
}
private static IEnumerable<T> FromSingleCollection<T>(T item) => new SingleCollection<T>(item);
private static IEnumerable<T> FromSingleCollectionValueEnumerator<T>(T item) => new SingleCollectionValueEnumerator<T>(item);
private static IEnumerable<T> FromValueSingleCollection<T>(T item) => new ValueSingleCollection<T>(item);
private static IEnumerable<T> FromValueSingleCollectionReferenceEnumerator<T>(T item) => new ValueSingleCollectionReferenceEnumerator<T>(item);
private static IEnumerable<T> FromReadonlyValueSingleCollection<T>(T item) => new ReadonlyValueSingleCollection<T>(item);
private static IEnumerable<T> FromReadonlyValueSingleCollectionReferenceEnumerator<T>(T item) => new ReadonlyValueSingleCollectionReferenceEnumerator<T>(item);
private static IEnumerable<T> FromSingleCollectionAndEnumerator<T>(T item) => new SingleCollectionAndEnumerator<T>(item);
}
/// <summary>
/// Value type enumerator for a single element.
/// </summary>
/// <typeparam name="T"></typeparam>
public struct ValueSingleEnumerator<T> : IEnumerator<T>
{
public ValueSingleEnumerator(T value) => _value = value;
private readonly T _value;
private bool _position;
public T Current => _position ? _value : throw new InvalidOperationException();
object? IEnumerator.Current => Current;
public bool MoveNext()
{
if (_position)
{
return false;
}
return _position = true;
}
public void Dispose() { }
public void Reset() => _position = false;
}
/// <summary>
/// Reference type enumerator for a single element.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class SingleEnumerator<T> : IEnumerator<T>
{
public SingleEnumerator(T value) => _value = value;
private readonly T _value;
private bool _position;
public T Current => _position ? _value : throw new InvalidOperationException();
object? IEnumerator.Current => Current;
public bool MoveNext()
{
if (_position)
{
return false;
}
return _position = true;
}
public void Dispose() { }
public void Reset() => _position = false;
}
/// <summary>
/// Collection of one item. This <see cref="IEnumerable{T}"/> is implemented
/// as a reference type with a reference type enumerator.
/// </summary>
/// <typeparam name="T">The element type of the collection.</typeparam>
public sealed class SingleCollection<T> : IEnumerable<T>
{
public SingleCollection(T value) => Value = value;
public readonly T Value;
public IEnumerator<T> GetEnumerator() => new SingleEnumerator<T>(Value);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
/// <summary>
/// Collection of one item. This <see cref="IEnumerable{T}"/> is implemented
/// as a reference type with a value type enumerator.
/// </summary>
/// <typeparam name="T">The element type of the collection.</typeparam>
public sealed class SingleCollectionValueEnumerator<T> : IEnumerable<T>
{
public SingleCollectionValueEnumerator(T value) => Value = value;
public readonly T Value;
public IEnumerator<T> GetEnumerator() => new ValueSingleEnumerator<T>(Value);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
/// <summary>
/// Collection of one item. This <see cref="IEnumerable{T}"/> is implemented
/// as a value type with a value type enumerator.
/// </summary>
/// <typeparam name="T">The element type of the collection.</typeparam>
public struct ValueSingleCollection<T> : IEnumerable<T>
{
public ValueSingleCollection(T value) => Value = value;
public readonly T Value;
public IEnumerator<T> GetEnumerator() => new ValueSingleEnumerator<T>(Value);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
/// <summary>
/// Collection of one item. This <see cref="IEnumerable{T}"/> is implemented
/// as a value type with a value type enumerator.
/// <br/>
/// This type is marked as <see langword="readonly"/> which causes a defensive
/// copy to be made and is bad for performance when we know the type will
/// always be cast to <see cref="IEnumerable{T}"/>.
/// </summary>
/// <typeparam name="T">The element type of the collection.</typeparam>
public readonly struct ReadonlyValueSingleCollection<T> : IEnumerable<T>
{
public ReadonlyValueSingleCollection(T value) => Value = value;
public readonly T Value;
public IEnumerator<T> GetEnumerator() => new ValueSingleEnumerator<T>(Value);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
/// <summary>
/// Collection of one item. This <see cref="IEnumerable{T}"/> is implemented
/// as a reference type with a reference type enumerator.
/// <br/>
/// This type is marked as <see langword="readonly"/> which causes a defensive
/// copy to be made and is bad for performance when we know the type will
/// always be cast to <see cref="IEnumerable{T}"/>.
/// </summary>
/// <typeparam name="T">The element type of the collection.</typeparam>
public readonly struct ReadonlyValueSingleCollectionReferenceEnumerator<T> : IEnumerable<T>
{
public ReadonlyValueSingleCollectionReferenceEnumerator(T value) => Value = value;
public readonly T Value;
public IEnumerator<T> GetEnumerator() => new SingleEnumerator<T>(Value);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
/// <summary>
/// Collection of one item. This <see cref="IEnumerable{T}"/> is implemented
/// as a reference type with a reference type enumerator.
/// </summary>
/// <typeparam name="T">The element type of the collection.</typeparam>
public struct ValueSingleCollectionReferenceEnumerator<T> : IEnumerable<T>
{
public ValueSingleCollectionReferenceEnumerator(T value) => Value = value;
public readonly T Value;
public IEnumerator<T> GetEnumerator() => new SingleEnumerator<T>(Value);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public sealed class SingleCollectionAndEnumerator<T> : IEnumerable<T>, IEnumerator<T>
{
public SingleCollectionAndEnumerator(T value) => Value = value;
public readonly T Value;
private byte _state;
public T Current => Value;
object? IEnumerator.Current => Current;
public bool MoveNext()
{
if (_state == 0)
{
_state = 1;
return true;
}
else if (_state == 1)
{
_state = byte.MaxValue;
return false;
}
else
{
throw new InvalidOperationException();
}
}
public IEnumerator<T> GetEnumerator() => this;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Reset() => _state = 0;
public void Dispose() { }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment