summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/src/JIT/Performance/CodeQuality/Span/Indexer.cs1021
-rw-r--r--tests/src/JIT/Performance/CodeQuality/Span/Indexer.csproj44
-rw-r--r--tests/src/JIT/config/benchmark/project.json1
3 files changed, 1066 insertions, 0 deletions
diff --git a/tests/src/JIT/Performance/CodeQuality/Span/Indexer.cs b/tests/src/JIT/Performance/CodeQuality/Span/Indexer.cs
new file mode 100644
index 0000000000..4a7264ba92
--- /dev/null
+++ b/tests/src/JIT/Performance/CodeQuality/Span/Indexer.cs
@@ -0,0 +1,1021 @@
+// 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 System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using Xunit;
+using Microsoft.Xunit.Performance;
+
+[assembly: OptimizeForBenchmarks]
+[assembly: MeasureInstructionsRetired]
+
+namespace Span
+{
+ class Sink
+ {
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static Sink NewSink() { return new Sink(); }
+
+ public byte b;
+ public int i;
+ }
+
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ class CategoryAttribute : Attribute
+ {
+ public CategoryAttribute(string name)
+ {
+ _name = name;
+ }
+ string _name;
+ public string Name => _name;
+ }
+
+ public class IndexerBench
+ {
+ const int Iterations = 1000000;
+ const int DefaultLength = 1024;
+ const byte Expected = 70;
+ static bool HasFailure = false;
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination")]
+ public static void Ref(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestRef(s);
+ }
+ return result;
+ },
+ "Ref({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestRef(Span<byte> data)
+ {
+ ref byte p = ref data.DangerousGetPinnableReference();
+ int length = data.Length;
+ byte x = 0;
+
+ for (var idx = 0; idx < length; idx++)
+ {
+ x ^= Unsafe.Add(ref p, idx);
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination")]
+ public static void Fixed1(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestFixed1(s);
+ }
+ return result;
+ },
+ "Fixed1({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static unsafe byte TestFixed1(Span<byte> data)
+ {
+ fixed (byte* pData = &data.DangerousGetPinnableReference())
+ {
+ int length = data.Length;
+ byte x = 0;
+ byte* p = pData;
+
+ for (var idx = 0; idx < length; idx++)
+ {
+ x ^= *(p + idx);
+ }
+
+ return x;
+ }
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination")]
+ public static void Fixed2(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestFixed2(s);
+ }
+ return result;
+ },
+ "Fixed2({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static unsafe byte TestFixed2(Span<byte> data)
+ {
+ fixed (byte* pData = &data.DangerousGetPinnableReference())
+ {
+ int length = data.Length;
+ byte x = 0;
+
+ for (var idx = 0; idx < length; idx++)
+ {
+ x ^= pData[idx];
+ }
+
+ return x;
+ }
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination")]
+ public static void Indexer1(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestIndexer1(s);
+ }
+ return result;
+ },
+ "Indexer1({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestIndexer1(Span<byte> data)
+ {
+ int length = data.Length;
+ byte x = 0;
+
+ for (var idx = 0; idx < length; idx++)
+ {
+ x ^= data[idx];
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination")]
+ public static void Indexer2(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestIndexer2(s);
+ }
+ return result;
+ },
+ "Indexer2({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestIndexer2(Span<byte> data)
+ {
+ byte x = 0;
+
+ for (var idx = 0; idx < data.Length; idx++)
+ {
+ x ^= data[idx];
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination")]
+ public static void Indexer3(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestIndexer3(s);
+ }
+ return result;
+ },
+ "Indexer3({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestIndexer3(Span<byte> data)
+ {
+ Span<byte> data2 = data;
+
+ byte x = 0;
+
+ for (var idx = 0; idx < data2.Length; idx++)
+ {
+ x ^= data2[idx];
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount=Iterations / 10)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination")]
+ public static void Indexer4(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ int inner = Math.Max(1, (innerIterationCount / 10));
+ for (int i = 0; i < inner ; ++i)
+ {
+ result = TestIndexer4(s, 10);
+ }
+ return result;
+ },
+ "Indexer4({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestIndexer4(Span<byte> data, int iterations)
+ {
+ byte x = 0;
+ int length = data.Length;
+
+ // This does more or less the same work as TestIndexer1
+ // but is expressed as a loop nest.
+ for (int i = 0; i < iterations; i++)
+ {
+ x = 0;
+
+ for (var idx = 0; idx < length; idx++)
+ {
+ x ^= data[idx];
+ }
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination")]
+ public static void Indexer5(int length)
+ {
+ byte[] a = GetData(length);
+ int z = 0;
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestIndexer5(s, out z);
+ }
+ return result;
+ },
+ "Indexer5({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestIndexer5(Span<byte> data, out int z)
+ {
+ byte x = 0;
+ z = -1;
+
+ // Write to z here should not be able to modify
+ // the span.
+ for (var idx = 0; idx < data.Length; idx++)
+ {
+ x ^= data[idx];
+ z = idx;
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination")]
+ public static void Indexer6(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestIndexer6(s);
+ }
+ return result;
+ },
+ "Indexer6({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestIndexer6(Span<byte> data)
+ {
+ byte x = 0;
+ Sink s = Sink.NewSink();
+
+ // Write to s.i here should not be able to modify
+ // the span.
+ for (var idx = 0; idx < data.Length; idx++)
+ {
+ x ^= data[idx];
+ s.i = 0;
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination")]
+ public static void ReadOnlyIndexer1(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ ReadOnlySpan<byte> s = new ReadOnlySpan<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestReadOnlyIndexer1(s);
+ }
+ return result;
+ },
+ "ReadOnlyIndexer1({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestReadOnlyIndexer1(ReadOnlySpan<byte> data)
+ {
+ int length = data.Length;
+ byte x = 0;
+
+ for (var idx = 0; idx < length; idx++)
+ {
+ x ^= data[idx];
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination")]
+ public static void ReadOnlyIndexer2(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ ReadOnlySpan<byte> s = new ReadOnlySpan<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestReadOnlyIndexer2(s);
+ }
+ return result;
+ },
+ "ReadOnlyIndexer2({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestReadOnlyIndexer2(ReadOnlySpan<byte> data)
+ {
+ byte x = 0;
+
+ for (var idx = 0; idx < data.Length; idx++)
+ {
+ x ^= data[idx];
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination w/ writes")]
+ public static void WriteViaIndexer1(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestWriteViaIndexer1(s);
+ }
+ return result;
+ },
+ "WriteViaIndexer1({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestWriteViaIndexer1(Span<byte> data)
+ {
+ byte q = data[0];
+
+ for (var idx = 1; idx < data.Length; idx++)
+ {
+ data[0] ^= data[idx];
+ }
+
+ byte x = data[0];
+ data[0] = q;
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Indexer in-loop bounds check elimination w/ writes")]
+ public static void WriteViaIndexer2(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestWriteViaIndexer2(s, 0, length);
+ }
+ return result;
+ },
+ "WriteViaIndexer2({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestWriteViaIndexer2(Span<byte> data, int start, int end)
+ {
+ byte x = 0;
+
+ for (var idx = start; idx < end; idx++)
+ {
+ // Bounds checks are redundant
+ byte b = data[idx];
+ x ^= b;
+ data[idx] = b;
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Span known size bounds check elimination")]
+ public static void KnownSizeArray(int length)
+ {
+ if (length != 1024)
+ {
+ throw new Exception("test requires 1024 byte length");
+ }
+
+ Invoke((int innerIterationCount) =>
+ {
+ byte result = TestKnownSizeArray(innerIterationCount);
+ return result;
+ },
+ "KnownSizeArray({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestKnownSizeArray(int innerIterationCount)
+ {
+ byte[] a = new byte[1024];
+ SetData(a);
+ Span<byte> data = new Span<byte>(a);
+ byte x = 0;
+
+ for (int i = 0; i < innerIterationCount; i++)
+ {
+ x = 0;
+ for (var idx = 0; idx < data.Length; idx++)
+ {
+ x ^= data[idx];
+ }
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Span known size bounds check elimination")]
+ public static void KnownSizeCtor(int length)
+ {
+ if (length < 1024)
+ {
+ throw new Exception("test requires at least 1024 byte length");
+ }
+
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ byte result = TestKnownSizeCtor(a, innerIterationCount);
+ return result;
+ },
+ "KnownSizeCtor({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestKnownSizeCtor(byte[] a, int innerIterationCount)
+ {
+ Span<byte> data = new Span<byte>(a, 0, 1024);
+ byte x = 0;
+
+ for (int i = 0; i < innerIterationCount; i++)
+ {
+ x = 0;
+ for (var idx = 0; idx < data.Length; idx++)
+ {
+ x ^= data[idx];
+ }
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Span known size bounds check elimination")]
+ public static void KnownSizeCtor2(int length)
+ {
+ if (length < 1024)
+ {
+ throw new Exception("test requires at least 1024 byte length");
+ }
+
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ byte result = TestKnownSizeCtor2(a, innerIterationCount);
+ return result;
+ },
+ "KnownSizeCtor2({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestKnownSizeCtor2(byte[] a, int innerIterationCount)
+ {
+ Span<byte> data1 = new Span<byte>(a, 0, 512);
+ Span<byte> data2 = new Span<byte>(a, 512, 512);
+ byte x = 0;
+
+ for (int i = 0; i < innerIterationCount; i++)
+ {
+ x = 0;
+ for (var idx = 0; idx < data1.Length; idx++)
+ {
+ x ^= data1[idx];
+ }
+ for (var idx = 0; idx < data2.Length; idx++)
+ {
+ x ^= data2[idx];
+ }
+ }
+
+ return x;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Same index in-loop redundant bounds check elimination")]
+ public static void SameIndex1(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestSameIndex1(s, 0, length);
+ }
+ return result;
+ },
+ "SameIndex1({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestSameIndex1(Span<byte> data, int start, int end)
+ {
+ byte x = 0;
+ byte y = 0;
+
+ for (var idx = start; idx < end; idx++)
+ {
+ x ^= data[idx];
+ y ^= data[idx];
+ }
+
+ byte t = (byte)(y ^ x ^ y);
+
+ return t;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Same index in-loop redundant bounds check elimination")]
+ public static void SameIndex2(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestSameIndex2(s, ref s[0], 0, length);
+ }
+ return result;
+ },
+ "SameIndex2({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestSameIndex2(Span<byte> data, ref byte b, int start, int end)
+ {
+ byte x = 0;
+ byte y = 0;
+ byte ye = 121;
+ byte q = data[0];
+
+ for (var idx = start; idx < end; idx++)
+ {
+ // Bounds check is redundant, but values are not CSEs.
+ x ^= data[idx];
+ b = 1;
+ y ^= data[idx];
+ }
+
+ byte t = (byte)(y ^ x ^ ye);
+ data[0] = q;
+
+ return t;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Covered index in-loop redundant bounds check elimination")]
+ public static void CoveredIndex1(int length)
+ {
+ if (length < 100)
+ {
+ throw new Exception("test requires at least 100 byte length");
+ }
+
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestCoveredIndex1(s, 0, length);
+ }
+ return result;
+ },
+ "CoveredIndex1({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestCoveredIndex1(Span<byte> data, int start, int end)
+ {
+ byte x = 0;
+ byte y = 0;
+
+ for (var idx = start; idx < end - 100; idx++)
+ {
+ x ^= data[idx + 100];
+ y ^= data[idx];
+ }
+
+ for (var idx = end - 100; idx < end; idx++)
+ {
+ y ^= data[idx];
+ x ^= data[idx - 100];
+ }
+
+ byte r = (byte)(x ^ y ^ x);
+
+ return r;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Covered index in-loop redundant bounds check elimination")]
+ public static void CoveredIndex2(int length)
+ {
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestCoveredIndex2(s, 0, length);
+ }
+ return result;
+ },
+ "CoveredIndex2({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestCoveredIndex2(Span<byte> data, int start, int end)
+ {
+ byte x = 0;
+ byte y = 0;
+
+ for (var idx = start; idx < end; idx++)
+ {
+ x ^= data[idx];
+
+ if (idx != 100)
+ {
+ // Should be able to eliminate this bounds check
+ y ^= data[0];
+ }
+ }
+
+ byte r = (byte)(y ^ x ^ y);
+
+ return r;
+ }
+
+ [Benchmark(InnerIterationCount = Iterations)]
+ [InlineData(DefaultLength)]
+ [Category("Covered index in-loop redundant bounds check elimination")]
+ public static void CoveredIndex3(int length)
+ {
+ if (length < 50)
+ {
+ throw new Exception("test requires at least 100 byte length");
+ }
+
+ byte[] a = GetData(length);
+
+ Invoke((int innerIterationCount) =>
+ {
+ Span<byte> s = new Span<byte>(a);
+ byte result = 0;
+ for (int i = 0; i < innerIterationCount; ++i)
+ {
+ result = TestCoveredIndex3(s, 0, length);
+ }
+ return result;
+ },
+ "CoveredIndex3({0})", length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static byte TestCoveredIndex3(Span<byte> data, int start, int end)
+ {
+ byte x = 0;
+ byte y = 0;
+ byte z = 0;
+
+ for (var idx = start; idx < end; idx++)
+ {
+ x ^= data[idx];
+
+ if (idx != 100)
+ {
+ y ^= data[50];
+ // Should be able to eliminate this bounds check
+ z ^= data[25];
+ }
+ }
+
+ byte r = (byte)(z ^ y ^ x ^ y ^ z);
+
+ return r;
+ }
+
+ // Invoke routine to abstract away the difference between running under xunit-perf vs running from the
+ // command line. Inner loop to be measured is taken as an Func<int, byte>, and invoked passing the number
+ // of iterations that the inner loop should execute.
+ static void Invoke(Func<int, byte> innerLoop, string nameFormat, params object[] nameArgs)
+ {
+ if (IsXunitInvocation)
+ {
+ foreach (var iteration in Benchmark.Iterations)
+ using (iteration.StartMeasurement())
+ innerLoop((int)Benchmark.InnerIterationCount);
+ }
+ else
+ {
+ if (DoWarmUp)
+ {
+ // Run some warm-up iterations before measuring
+ innerLoop(CommandLineInnerIterationCount);
+ // Clear the flag since we're now warmed up (caller will
+ // reset it before calling new code)
+ DoWarmUp = false;
+ }
+
+ // Now do the timed run of the inner loop.
+ Stopwatch sw = Stopwatch.StartNew();
+ byte check = innerLoop(CommandLineInnerIterationCount);
+ sw.Stop();
+
+ // Print result.
+ string name = String.Format(nameFormat, nameArgs);
+ double timeInMs = sw.Elapsed.TotalMilliseconds;
+ Console.Write("{0,25}: {1,7:F2}ms", name, timeInMs);
+
+ bool failed = (check != Expected);
+ if (failed)
+ {
+ Console.Write(" -- failed to validate, got {0} expected {1}", check, Expected);
+ HasFailure = true;
+ }
+ Console.WriteLine();
+ }
+ }
+
+ static byte[] GetData(int size)
+ {
+ byte[] data = new byte[size];
+ SetData(data);
+ return data;
+ }
+
+ static void SetData(byte[] data)
+ {
+ Random Rnd = new Random(42);
+ Rnd.NextBytes(data);
+ }
+
+ static bool IsXunitInvocation = true;
+ static int CommandLineInnerIterationCount = 1;
+ static bool DoWarmUp;
+
+ public static void Usage()
+ {
+ Console.WriteLine(" pass -bench for benchmark mode w/default iterations");
+ Console.WriteLine(" pass [#iterations] for benchmark mode w/iterations");
+ Console.WriteLine();
+ }
+
+ public static int Main(string[] args)
+ {
+ if (args.Length > 0)
+ {
+ if (args[0].Equals("-bench"))
+ {
+ CommandLineInnerIterationCount = Iterations;
+ }
+ else
+ {
+ bool parsed = Int32.TryParse(args[0], out CommandLineInnerIterationCount);
+ if (!parsed)
+ {
+ Usage();
+ return -1;
+ }
+ }
+
+ Console.WriteLine("Running as command line perf test: {0} iterations",
+ CommandLineInnerIterationCount);
+ Console.WriteLine();
+ }
+ else
+ {
+ Console.WriteLine("Running as correctness test: {0} iterations",
+ CommandLineInnerIterationCount);
+ Usage();
+ }
+
+ // When we call into Invoke, it'll need to know this isn't xunit-perf running
+ IsXunitInvocation = false;
+
+ // Discover what tests to run via reflection
+ TypeInfo t = typeof(IndexerBench).GetTypeInfo();
+
+ var testsByCategory = new Dictionary<string, List<MethodInfo>>();
+
+ // Do a first pass to find out what categories of benchmarks we have.
+ foreach(MethodInfo m in t.DeclaredMethods)
+ {
+ BenchmarkAttribute benchAttr = m.GetCustomAttribute<BenchmarkAttribute>();
+ if (benchAttr != null)
+ {
+ string category = "none";
+ CategoryAttribute categoryAttr = m.GetCustomAttribute<CategoryAttribute>();
+ if (categoryAttr != null)
+ {
+ category = categoryAttr.Name;
+ }
+
+ List<MethodInfo> tests = null;
+
+ if (!testsByCategory.ContainsKey(category))
+ {
+ tests = new List<MethodInfo>();
+ testsByCategory.Add(category, tests);
+ }
+ else
+ {
+ tests = testsByCategory[category];
+ }
+
+ tests.Add(m);
+ }
+ }
+
+ foreach(string categoryName in testsByCategory.Keys)
+ {
+ Console.WriteLine("**** {0} ****", categoryName);
+
+ foreach(MethodInfo m in testsByCategory[categoryName])
+ {
+ // Request a warm-up iteration before measuring this benchmark method.
+ DoWarmUp = true;
+
+ // Get the benchmark to measure as a delegate taking the number of inner-loop iterations to run
+ var invokeMethod = m.CreateDelegate(typeof(Action<int>)) as Action<int>;
+
+ // All the benchmarks methods in this test use [InlineData] to specify how many times and with
+ // what arguments they should be run.
+ foreach (InlineDataAttribute dataAttr in m.GetCustomAttributes<InlineDataAttribute>())
+ {
+ foreach (object[] data in dataAttr.GetData(m))
+ {
+ // All the benchmark methods in this test take a single int parameter
+ invokeMethod((int)data[0]);
+ }
+ }
+ }
+
+ Console.WriteLine();
+ }
+
+ if (HasFailure)
+ {
+ Console.WriteLine("Some tests failed validation");
+ return -1;
+ }
+
+ return 100;
+ }
+ }
+}
diff --git a/tests/src/JIT/Performance/CodeQuality/Span/Indexer.csproj b/tests/src/JIT/Performance/CodeQuality/Span/Indexer.csproj
new file mode 100644
index 0000000000..a871713d1d
--- /dev/null
+++ b/tests/src/JIT/Performance/CodeQuality/Span/Indexer.csproj
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!-- Always use latest Roslyn compiler -->
+ <Import Project="..\..\..\..\..\..\Tools\net46\roslyn\build\Microsoft.Net.Compilers.props"/>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+ <NuGetTargetMoniker>.NETStandard,Version=v1.4</NuGetTargetMoniker>
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ </PropertyGroup>
+ <PropertyGroup>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Indexer.cs" />
+ </ItemGroup>
+ <PropertyGroup>
+ <ProjectJson>$(JitPackagesConfigFileDirectory)benchmark\project.json</ProjectJson>
+ <ProjectLockJson>$(JitPackagesConfigFileDirectory)benchmark\project.lock.json</ProjectLockJson>
+ </PropertyGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' ">
+ </PropertyGroup>
+</Project>
diff --git a/tests/src/JIT/config/benchmark/project.json b/tests/src/JIT/config/benchmark/project.json
index 3b218fc203..09e5d8c418 100644
--- a/tests/src/JIT/config/benchmark/project.json
+++ b/tests/src/JIT/config/benchmark/project.json
@@ -18,6 +18,7 @@
"System.Reflection.Extensions": "4.4.0-beta-24913-02",
"System.Reflection.TypeExtensions": "4.4.0-preview1-25214-03",
"System.Runtime": "4.4.0-beta-24913-02",
+ "System.Runtime.CompilerServices.Unsafe": "4.4.0-preview1-25210-01",
"System.Runtime.Extensions": "4.4.0-beta-24913-02",
"System.Runtime.Numerics": "4.4.0-beta-24913-02",
"System.Text.RegularExpressions": "4.4.0-beta-24913-02",