From 8ce344834f1eba1b9302f39252fcb745e0be497b Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Thu, 27 Apr 2023 11:49:20 +0200 Subject: [PATCH] Add a few struct promotion related benchmarks --- .../runtime/Struct/FilteredSpanEnumerator.cs | 68 ++++++ src/benchmarks/micro/runtime/Struct/GSeq.cs | 215 ++++++++++++++++++ .../micro/runtime/Struct/SpanWrapper.cs | 62 +++++ 3 files changed, 345 insertions(+) create mode 100644 src/benchmarks/micro/runtime/Struct/FilteredSpanEnumerator.cs create mode 100644 src/benchmarks/micro/runtime/Struct/GSeq.cs create mode 100644 src/benchmarks/micro/runtime/Struct/SpanWrapper.cs diff --git a/src/benchmarks/micro/runtime/Struct/FilteredSpanEnumerator.cs b/src/benchmarks/micro/runtime/Struct/FilteredSpanEnumerator.cs new file mode 100644 index 0000000000..4078a50747 --- /dev/null +++ b/src/benchmarks/micro/runtime/Struct/FilteredSpanEnumerator.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using BenchmarkDotNet.Attributes; +using MicroBenchmarks; +using System; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Struct +{ + [BenchmarkCategory(Categories.Runtime, Categories.JIT)] + public class FilteredSpanEnumerator + { + private int[] _array; + private int[] _inds; + + [GlobalSetup] + public void Setup() + { + _array = Enumerable.Range(0, 10000).ToArray(); + _inds = Enumerable.Range(0, 10000).ToArray(); + } + + [Benchmark] + public int Sum() + { + int sum = 0; + foreach (int s in new FilteredSpanEnumerator(_array, _inds)) + { + sum += s; + } + + return sum; + } + } + + public ref struct FilteredSpanEnumerator + { + private readonly ReadOnlySpan arr; + private readonly int[] inds; + + private T current; + private int i; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public FilteredSpanEnumerator(ReadOnlySpan arr, int[] inds) { + this.arr = arr; + this.inds = inds; + current = default; + i = 0; + } + + public T Current => current; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() { + if (i >= inds.Length) + return false; + + current = arr[inds[i++]]; + return true; + } + + public FilteredSpanEnumerator GetEnumerator() => this; + } +} diff --git a/src/benchmarks/micro/runtime/Struct/GSeq.cs b/src/benchmarks/micro/runtime/Struct/GSeq.cs new file mode 100644 index 0000000000..6bc573e797 --- /dev/null +++ b/src/benchmarks/micro/runtime/Struct/GSeq.cs @@ -0,0 +1,215 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using BenchmarkDotNet.Attributes; +using MicroBenchmarks; +using System.Collections.Generic; +using System.Collections; +using System.Linq; + +namespace Struct +{ + [BenchmarkCategory(Categories.Runtime, Categories.JIT)] + public class GSeq + { + private int[] _array; + + [GlobalSetup] + public void Setup() + { + _array = Enumerable.Range(0, 10000).ToArray(); + } + + [Benchmark] + public int FilterSkipMapSum() + { + var arr = new ArrayEnumerator(_array); + var filter = new FilterEnumerator, FilterIsOdd>(arr, new FilterIsOdd()); + var skip = new SkipEnumerator, FilterIsOdd>>(5, filter); + var map = + new MapEnumerator, FilterIsOdd>>, Plus15Mapper>( + skip, + new Plus15Mapper()); + return Fold2, FilterIsOdd>>, + Plus15Mapper>, FoldPlus>(map, 0, new FoldPlus()); + } + + public static TOut Fold2(TEnumerator enumerator, TOut initial, + TFolder folder) + where TEnumerator : struct, IEnumerator + where TFolder : struct, IInvokable + { + while (enumerator.MoveNext()) + initial = folder.Invoke(initial, enumerator.Current); + + return initial; + } + } + + public interface IInvokable + { + TOut Invoke(TIn1 value); + } + + public interface IInvokable + { + TOut Invoke(TIn1 value1, TIn2 value2); + } + + + public readonly struct Plus15Mapper : IInvokable + { + public int Invoke(int value) => value + 15; + } + + public readonly struct FilterIsOdd : IInvokable + { + public bool Invoke(int value) => value % 2 == 1; + } + + public readonly struct FoldPlus : IInvokable + { + public int Invoke(int value1, int value2) => value1 + value2; + } + + public struct ArrayEnumerator : IEnumerator + { + private readonly T[] _array; + private int _count; + + public ArrayEnumerator(T[] array) + { + _array = array; + _count = -1; + } + + + public bool MoveNext() + { + _count++; + return (uint)_count < (uint)_array.Length; + } + + public void Reset() + { + } + + public T Current => _array[_count]; + + object IEnumerator.Current => Current; + + public void Dispose() + { + } + } + + public struct SkipEnumerator : IEnumerator + where TEnumerator : struct, IEnumerator + { + private TEnumerator _enumerator; + private int _skipCount; + + public SkipEnumerator(int skipCount, TEnumerator enumerator) + { + _skipCount = skipCount; + _enumerator = enumerator; + } + + public bool MoveNext() + { + while (_skipCount > 0) + { + if (!_enumerator.MoveNext()) + return false; + _skipCount--; + } + + return _enumerator.MoveNext(); + } + + public void Reset() + { + } + + public T Current => _enumerator.Current; + + object IEnumerator.Current => Current; + + public void Dispose() + { + } + } + + public struct MapEnumerator : IEnumerator + where TEnumerator : struct, IEnumerator + where TMapper : struct, IInvokable + { + private TEnumerator _enumerator; + private TMapper _mapper; + + public MapEnumerator(TEnumerator enumerator, TMapper mapper) + { + _enumerator = enumerator; + _mapper = mapper; + } + + public bool MoveNext() + { + return _enumerator.MoveNext(); + } + + public void Reset() + { + } + + public TOut Current => _mapper.Invoke(_enumerator.Current); + + object IEnumerator.Current => Current; + + public void Dispose() + { + } + } + + public struct FilterEnumerator : IEnumerator + where TEnumerator : struct, IEnumerator + where TFilter : struct, IInvokable + { + private TEnumerator _enumerator; + private TFilter _filter; + + public FilterEnumerator(TEnumerator enumerator, TFilter filter) + { + _enumerator = enumerator; + _filter = filter; + } + + public bool MoveNext() + { + while (_enumerator.MoveNext()) + { + if (_filter.Invoke(_enumerator.Current)) + return true; + } + + return false; + } + + public void Reset() + { + } + + public T Current => _enumerator.Current; + + object IEnumerator.Current => Current; + + public void Dispose() + { + } + } + +} diff --git a/src/benchmarks/micro/runtime/Struct/SpanWrapper.cs b/src/benchmarks/micro/runtime/Struct/SpanWrapper.cs new file mode 100644 index 0000000000..c7cd841e16 --- /dev/null +++ b/src/benchmarks/micro/runtime/Struct/SpanWrapper.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using BenchmarkDotNet.Attributes; +using MicroBenchmarks; +using System.Linq; +using System; +using System.Runtime.CompilerServices; + +namespace Struct +{ + [BenchmarkCategory(Categories.Runtime, Categories.JIT)] + public class SpanWrapper + { + private int[] _array; + + [GlobalSetup] + public void Setup() + { + _array = Enumerable.Range(0, 10000).ToArray(); + } + + [Benchmark] + public int BaselineSum() + { + return SumSpan(_array); + } + + [Benchmark] + public int WrapperSum() + { + return SumSpanWrapper(new SpanWrapper { Span = _array }); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private int SumSpan(ReadOnlySpan span) + { + int sum = 0; + foreach (int val in span) + sum += val; + + return sum; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private int SumSpanWrapper(SpanWrapper spanWrapper) + { + int sum = 0; + foreach (int val in spanWrapper) + sum += val; + + return sum; + } + } + + public ref struct SpanWrapper + { + public ReadOnlySpan Span; + public ReadOnlySpan.Enumerator GetEnumerator() => Span.GetEnumerator(); + } +}