Closed jakobbotsch closed 4 months ago
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch See info in area-owners.md if you want to be subscribed.
There is a question of whether we can optimize Span<T>
as well as T[]
without introducing (more) special status for Span<T>
/ReadOnlySpan<T>
. That's because the transformation shown above is actually illegal for the JIT to do unless we make it undefined behavior for a Span<T>
to exist with an "invalid" range of managed byrefs.
Consider the following example:
static void Main()
{
int[] values = [1, 2, 3, 4, 0];
Span<int> exampleSpan = MemoryMarshal.CreateSpan(ref values[0], int.MaxValue);
Sum(exampleSpan); // No problem today
Sum2(exampleSpan); // Forms illegal byref
}
private static int Sum(Span<int> s)
{
int sum = 0;
foreach (int x in s)
{
if (x == 0)
break;
sum += x;
}
return sum;
}
private static int Sum2(Span<int> s)
{
int sum = 0;
ref int p = ref MemoryMarshal.GetReference(s);
ref int end = ref Unsafe.Add(ref p, s.Length);
while (Unsafe.IsAddressLessThan(ref p, ref end))
{
int x = p;
if (x == 0)
break;
sum += x;
p = ref Unsafe.Add(ref p, 1);
}
return sum;
}
exampleSpan
is created with a valid byref but a length that makes _reference + length
an invalid byref. Today, there is no problem in Sum
because we do not eagerly form the _reference + length
byref, but Sum2
ends up eagerly forming this illegal byref.
The strength reduction optimization would have the JIT transform Sum
to Sum2
.
@jkotas @davidwrighton any thoughts on this? Can we document somewhere that Span<T>
/ReadOnlySpan<T>
have "special status" to make them amenable to optimizations to a similar level to T[]
? I think we would document two things:
Span<T>
, i.e. _reference + length
must point inside (or at the end of) the same object as _reference
when it is a managed byref.The existing Span uses do not always follow this restriction. For example:
I guess we can document it retroactively as a breaking change and try to fix all instances of the bad patterns that we can find.
Hmm, I'll have to see if that seems to be worth it once I get further. I can start out with arrays for now to do the measurements.
I think instead of forming end = span._reference + span.length * size
, we can just utilize a reverse counted loop and come out equal on x64/arm64. For example, Sum2
will usually end up as
private static int Sum2(Span<int> s)
{
int sum = 0;
ref int p = ref MemoryMarshal.GetReference(s);
if (s.Length > 0)
{
int length = s.Length;
do
{
int x = p;
if (x == 0)
break;
sum += x;
p = ref Unsafe.Add(ref p, 1);
} while (--length > 0);
}
return sum;
}
when loop inversion is kicking in. The --length > 0
can be done in 2 instructions + 1 live variable on arm64/x64, exactly the same as if we had formed end
.
We sadly still have the problem described above for Span<T>
. Without the assumption that a Span<T>
points within the same managed object it is illegal to transform
public static int Sum(Span<int> span, Func<int, bool> sumIndex)
{
for (int i = 0; i < span.Length; i++)
sum += sumIndex(i) ? span[i] : 0;
return sum;
}
into
public static int Sum(Span<int> span, Func<int, bool> sumIndex)
{
ref int val = ref span[0];
for (int i = 0; i < span.Length; i++)
{
sum += sumIndex(i) ? val : 0;
val = ref Unsafe.Add(ref val, 1);
}
return sum;
}
The same transformation seems ok for arrays.
(Of course whether or not this transformation is profitable is another question entirely.)
@EgorBot -intel -amd -commit 57f870f909dbfad35142e5aaa6e681464de4f439 vs 82ce118743cbd8f8261b6fb38fe0b0ec08d2030b --disasm
// 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 System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Loops
{
[GroupBenchmarksBy(BenchmarkDotNet.Configs.BenchmarkLogicalGroupRule.ByCategory)]
public class StrengthReduction
{
private short[] _arrayShorts;
private int[] _arrayInts;
private long[] _arrayLongs;
private S3[] _arrayS3;
private S8[] _arrayS8;
private S12[] _arrayS12;
private S16[] _arrayS16;
private S29[] _arrayS29;
[GlobalSetup]
public void Setup()
{
_arrayShorts = Enumerable.Range(0, 10000).Select(i => (short)i).ToArray();
_arrayInts = Enumerable.Range(0, 10000).Select(i => i).ToArray();
_arrayLongs = Enumerable.Range(0, 10000).Select(i => (long)i).ToArray();
_arrayS3 = Enumerable.Range(0, 10000).Select(i => new S3 { A = (byte)i, B = (byte)i, C = (byte)i }).ToArray();
_arrayS8 = Enumerable.Range(0, 10000).Select(i => new S8 { A = i, B = i, }).ToArray();
_arrayS12 = Enumerable.Range(0, 10000).Select(i => new S12 { A = i, B = i, C = i, }).ToArray();
_arrayS16 = Enumerable.Range(0, 10000).Select(i => new S16 { A = i, B = i, }).ToArray();
_arrayS29 = Enumerable.Range(0, 10000).Select(i => new S29 { A = (byte)i, }).ToArray();
}
[Benchmark(Baseline = true), BenchmarkCategory("short")]
public int SumShortsArray()
{
return SumShortsWithArray(_arrayShorts);
}
[Benchmark, BenchmarkCategory("short")]
public int SumShortsSpan()
{
return SumShortsWithSpan(_arrayShorts);
}
[Benchmark, BenchmarkCategory("short")]
public int SumShortsArrayStrengthReduced()
{
return SumShortsStrengthReducedWithArray(_arrayShorts);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsWithArray(short[] input)
{
int result = 0;
// 'or' by 1 to make loop body slightly larger to work around
// https://github.com/dotnet/runtime/issues/104665
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsWithSpan(ReadOnlySpan<short> input)
{
int result = 0;
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsStrengthReducedWithArray(short[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref short p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("int")]
public int SumIntsArray()
{
return SumIntsWithArray(_arrayInts);
}
[Benchmark, BenchmarkCategory("int")]
public int SumIntsSpan()
{
return SumIntsWithSpan(_arrayInts);
}
[Benchmark, BenchmarkCategory("int")]
public int SumIntsArrayStrengthReduced()
{
return SumIntsStrengthReducedWithArray(_arrayInts);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumIntsWithArray(int[] input)
{
int result = 0;
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumIntsWithSpan(ReadOnlySpan<int> input)
{
int result = 0;
foreach (int s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumIntsStrengthReducedWithArray(int[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref int p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("long")]
public long SumLongsArray()
{
return SumLongsWithArray(_arrayLongs);
}
[Benchmark, BenchmarkCategory("long")]
public long SumLongsSpan()
{
return SumLongsWithSpan(_arrayLongs);
}
[Benchmark, BenchmarkCategory("long")]
public long SumLongsArrayStrengthReduced()
{
return SumLongsStrengthReducedWithArray(_arrayLongs);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumLongsWithArray(long[] input)
{
long result = 0;
foreach (long s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumLongsWithSpan(ReadOnlySpan<long> input)
{
int result = 0;
foreach (int s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumLongsStrengthReducedWithArray(long[] input)
{
long result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref long p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S3")]
public int SumS3Array()
{
return SumS3WithArray(_arrayS3);
}
[Benchmark, BenchmarkCategory("S3")]
public int SumS3Span()
{
return SumS3WithSpan(_arrayS3);
}
[Benchmark, BenchmarkCategory("S3")]
public int SumS3ArrayStrengthReduced()
{
return SumS3StrengthReducedWithArray(_arrayS3);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS3WithArray(S3[] input)
{
int result = 0;
foreach (S3 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS3WithSpan(ReadOnlySpan<S3> input)
{
int result = 0;
foreach (S3 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS3StrengthReducedWithArray(S3[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S3 p = ref input[0];
do
{
S3 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S8")]
public int SumS8Array()
{
return SumS8WithArray(_arrayS8);
}
[Benchmark, BenchmarkCategory("S8")]
public int SumS8Span()
{
return SumS8WithSpan(_arrayS8);
}
[Benchmark, BenchmarkCategory("S8")]
public int SumS8ArrayStrengthReduced()
{
return SumS8StrengthReducedWithArray(_arrayS8);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS8WithArray(S8[] input)
{
int result = 0;
foreach (S8 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS8WithSpan(ReadOnlySpan<S8> input)
{
int result = 0;
foreach (S8 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS8StrengthReducedWithArray(S8[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S8 p = ref input[0];
do
{
S8 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S12")]
public int SumS12Array()
{
return SumS12WithArray(_arrayS12);
}
[Benchmark, BenchmarkCategory("S12")]
public int SumS12Span()
{
return SumS12WithSpan(_arrayS12);
}
[Benchmark, BenchmarkCategory("S12")]
public int SumS12ArrayStrengthReduced()
{
return SumS12StrengthReducedWithArray(_arrayS12);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS12WithArray(S12[] input)
{
int result = 0;
foreach (S12 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS12WithSpan(ReadOnlySpan<S12> input)
{
int result = 0;
foreach (S12 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS12StrengthReducedWithArray(S12[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S12 p = ref input[0];
do
{
S12 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S16")]
public long SumS16Array()
{
return SumS16WithArray(_arrayS16);
}
[Benchmark, BenchmarkCategory("S16")]
public long SumS16Span()
{
return SumS16WithSpan(_arrayS16);
}
[Benchmark, BenchmarkCategory("S16")]
public long SumS16ArrayStrengthReduced()
{
return SumS16StrengthReducedWithArray(_arrayS16);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumS16WithArray(S16[] input)
{
long result = 0;
foreach (S16 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumS16WithSpan(ReadOnlySpan<S16> input)
{
long result = 0;
foreach (S16 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumS16StrengthReducedWithArray(S16[] input)
{
long result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S16 p = ref input[0];
do
{
S16 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S29")]
public int SumS29Array()
{
int sum = 0;
//for (int i = 0; i < 100; i++)
sum += SumS29WithArray(_arrayS29);
return sum;
}
[Benchmark, BenchmarkCategory("S29")]
public int SumS29Span()
{
int sum = 0;
//for (int i = 0; i < 100; i++)
sum += SumS29WithSpan(_arrayS29);
return sum;
}
[Benchmark, BenchmarkCategory("S29")]
public int SumS29ArrayStrengthReduced()
{
int sum = 0;
//for (int i = 0; i < 100; i++)
sum += SumS29StrengthReducedWithArray(_arrayS29);
return sum;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS29WithArray(S29[] input)
{
int result = 0;
foreach (S29 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS29WithSpan(ReadOnlySpan<S29> input)
{
int result = 0;
foreach (S29 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS29StrengthReducedWithArray(S29[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S29 p = ref input[0];
do
{
S29 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
private struct S3
{
public byte A, B, C;
}
public struct S8
{
public int A, B;
}
public struct S12
{
public int A, B, C;
}
public struct S16
{
public long A, B;
}
[StructLayout(LayoutKind.Sequential, Size = 29)]
public struct S29
{
public byte A;
}
}
}
@EgorBot -arm64 -commit 57f870f909dbfad35142e5aaa6e681464de4f439 vs 82ce118743cbd8f8261b6fb38fe0b0ec08d2030b --disasm
// 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 System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Loops
{
[GroupBenchmarksBy(BenchmarkDotNet.Configs.BenchmarkLogicalGroupRule.ByCategory)]
public class StrengthReduction
{
private short[] _arrayShorts;
private int[] _arrayInts;
private long[] _arrayLongs;
private S3[] _arrayS3;
private S8[] _arrayS8;
private S12[] _arrayS12;
private S16[] _arrayS16;
private S29[] _arrayS29;
[GlobalSetup]
public void Setup()
{
_arrayShorts = Enumerable.Range(0, 10000).Select(i => (short)i).ToArray();
_arrayInts = Enumerable.Range(0, 10000).Select(i => i).ToArray();
_arrayLongs = Enumerable.Range(0, 10000).Select(i => (long)i).ToArray();
_arrayS3 = Enumerable.Range(0, 10000).Select(i => new S3 { A = (byte)i, B = (byte)i, C = (byte)i }).ToArray();
_arrayS8 = Enumerable.Range(0, 10000).Select(i => new S8 { A = i, B = i, }).ToArray();
_arrayS12 = Enumerable.Range(0, 10000).Select(i => new S12 { A = i, B = i, C = i, }).ToArray();
_arrayS16 = Enumerable.Range(0, 10000).Select(i => new S16 { A = i, B = i, }).ToArray();
_arrayS29 = Enumerable.Range(0, 10000).Select(i => new S29 { A = (byte)i, }).ToArray();
}
[Benchmark(Baseline = true), BenchmarkCategory("short")]
public int SumShortsArray()
{
return SumShortsWithArray(_arrayShorts);
}
[Benchmark, BenchmarkCategory("short")]
public int SumShortsSpan()
{
return SumShortsWithSpan(_arrayShorts);
}
[Benchmark, BenchmarkCategory("short")]
public int SumShortsArrayStrengthReduced()
{
return SumShortsStrengthReducedWithArray(_arrayShorts);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsWithArray(short[] input)
{
int result = 0;
// 'or' by 1 to make loop body slightly larger to work around
// https://github.com/dotnet/runtime/issues/104665
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsWithSpan(ReadOnlySpan<short> input)
{
int result = 0;
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsStrengthReducedWithArray(short[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref short p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("int")]
public int SumIntsArray()
{
return SumIntsWithArray(_arrayInts);
}
[Benchmark, BenchmarkCategory("int")]
public int SumIntsSpan()
{
return SumIntsWithSpan(_arrayInts);
}
[Benchmark, BenchmarkCategory("int")]
public int SumIntsArrayStrengthReduced()
{
return SumIntsStrengthReducedWithArray(_arrayInts);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumIntsWithArray(int[] input)
{
int result = 0;
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumIntsWithSpan(ReadOnlySpan<int> input)
{
int result = 0;
foreach (int s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumIntsStrengthReducedWithArray(int[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref int p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("long")]
public long SumLongsArray()
{
return SumLongsWithArray(_arrayLongs);
}
[Benchmark, BenchmarkCategory("long")]
public long SumLongsSpan()
{
return SumLongsWithSpan(_arrayLongs);
}
[Benchmark, BenchmarkCategory("long")]
public long SumLongsArrayStrengthReduced()
{
return SumLongsStrengthReducedWithArray(_arrayLongs);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumLongsWithArray(long[] input)
{
long result = 0;
foreach (long s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumLongsWithSpan(ReadOnlySpan<long> input)
{
int result = 0;
foreach (int s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumLongsStrengthReducedWithArray(long[] input)
{
long result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref long p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S3")]
public int SumS3Array()
{
return SumS3WithArray(_arrayS3);
}
[Benchmark, BenchmarkCategory("S3")]
public int SumS3Span()
{
return SumS3WithSpan(_arrayS3);
}
[Benchmark, BenchmarkCategory("S3")]
public int SumS3ArrayStrengthReduced()
{
return SumS3StrengthReducedWithArray(_arrayS3);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS3WithArray(S3[] input)
{
int result = 0;
foreach (S3 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS3WithSpan(ReadOnlySpan<S3> input)
{
int result = 0;
foreach (S3 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS3StrengthReducedWithArray(S3[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S3 p = ref input[0];
do
{
S3 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S8")]
public int SumS8Array()
{
return SumS8WithArray(_arrayS8);
}
[Benchmark, BenchmarkCategory("S8")]
public int SumS8Span()
{
return SumS8WithSpan(_arrayS8);
}
[Benchmark, BenchmarkCategory("S8")]
public int SumS8ArrayStrengthReduced()
{
return SumS8StrengthReducedWithArray(_arrayS8);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS8WithArray(S8[] input)
{
int result = 0;
foreach (S8 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS8WithSpan(ReadOnlySpan<S8> input)
{
int result = 0;
foreach (S8 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS8StrengthReducedWithArray(S8[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S8 p = ref input[0];
do
{
S8 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S12")]
public int SumS12Array()
{
return SumS12WithArray(_arrayS12);
}
[Benchmark, BenchmarkCategory("S12")]
public int SumS12Span()
{
return SumS12WithSpan(_arrayS12);
}
[Benchmark, BenchmarkCategory("S12")]
public int SumS12ArrayStrengthReduced()
{
return SumS12StrengthReducedWithArray(_arrayS12);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS12WithArray(S12[] input)
{
int result = 0;
foreach (S12 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS12WithSpan(ReadOnlySpan<S12> input)
{
int result = 0;
foreach (S12 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS12StrengthReducedWithArray(S12[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S12 p = ref input[0];
do
{
S12 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S16")]
public long SumS16Array()
{
return SumS16WithArray(_arrayS16);
}
[Benchmark, BenchmarkCategory("S16")]
public long SumS16Span()
{
return SumS16WithSpan(_arrayS16);
}
[Benchmark, BenchmarkCategory("S16")]
public long SumS16ArrayStrengthReduced()
{
return SumS16StrengthReducedWithArray(_arrayS16);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumS16WithArray(S16[] input)
{
long result = 0;
foreach (S16 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumS16WithSpan(ReadOnlySpan<S16> input)
{
long result = 0;
foreach (S16 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumS16StrengthReducedWithArray(S16[] input)
{
long result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S16 p = ref input[0];
do
{
S16 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S29")]
public int SumS29Array()
{
int sum = 0;
//for (int i = 0; i < 100; i++)
sum += SumS29WithArray(_arrayS29);
return sum;
}
[Benchmark, BenchmarkCategory("S29")]
public int SumS29Span()
{
int sum = 0;
//for (int i = 0; i < 100; i++)
sum += SumS29WithSpan(_arrayS29);
return sum;
}
[Benchmark, BenchmarkCategory("S29")]
public int SumS29ArrayStrengthReduced()
{
int sum = 0;
//for (int i = 0; i < 100; i++)
sum += SumS29StrengthReducedWithArray(_arrayS29);
return sum;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS29WithArray(S29[] input)
{
int result = 0;
foreach (S29 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS29WithSpan(ReadOnlySpan<S29> input)
{
int result = 0;
foreach (S29 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS29StrengthReducedWithArray(S29[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S29 p = ref input[0];
do
{
S29 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
private struct S3
{
public byte A, B, C;
}
public struct S8
{
public int A, B;
}
public struct S12
{
public int A, B, C;
}
public struct S16
{
public long A, B;
}
[StructLayout(LayoutKind.Sequential, Size = 29)]
public struct S29
{
public byte A;
}
}
}
@EgorBot -intel -commit 57f870f909dbfad35142e5aaa6e681464de4f439 vs 82ce118743cbd8f8261b6fb38fe0b0ec08d2030b --disasm
// 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 System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Loops
{
[GroupBenchmarksBy(BenchmarkDotNet.Configs.BenchmarkLogicalGroupRule.ByCategory)]
public class StrengthReduction
{
private short[] _arrayShorts;
private int[] _arrayInts;
private long[] _arrayLongs;
private S3[] _arrayS3;
private S8[] _arrayS8;
private S12[] _arrayS12;
private S16[] _arrayS16;
private S29[] _arrayS29;
[GlobalSetup]
public void Setup()
{
_arrayShorts = Enumerable.Range(0, 10000).Select(i => (short)i).ToArray();
_arrayInts = Enumerable.Range(0, 10000).Select(i => i).ToArray();
_arrayLongs = Enumerable.Range(0, 10000).Select(i => (long)i).ToArray();
_arrayS3 = Enumerable.Range(0, 10000).Select(i => new S3 { A = (byte)i, B = (byte)i, C = (byte)i }).ToArray();
_arrayS8 = Enumerable.Range(0, 10000).Select(i => new S8 { A = i, B = i, }).ToArray();
_arrayS12 = Enumerable.Range(0, 10000).Select(i => new S12 { A = i, B = i, C = i, }).ToArray();
_arrayS16 = Enumerable.Range(0, 10000).Select(i => new S16 { A = i, B = i, }).ToArray();
_arrayS29 = Enumerable.Range(0, 10000).Select(i => new S29 { A = (byte)i, }).ToArray();
}
[Benchmark(Baseline = true), BenchmarkCategory("short")]
public int SumShortsArray()
{
return SumShortsWithArray(_arrayShorts);
}
[Benchmark, BenchmarkCategory("short")]
public int SumShortsSpan()
{
return SumShortsWithSpan(_arrayShorts);
}
[Benchmark, BenchmarkCategory("short")]
public int SumShortsArrayStrengthReduced()
{
return SumShortsStrengthReducedWithArray(_arrayShorts);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsWithArray(short[] input)
{
int result = 0;
// 'or' by 1 to make loop body slightly larger to work around
// https://github.com/dotnet/runtime/issues/104665
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsWithSpan(ReadOnlySpan<short> input)
{
int result = 0;
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsStrengthReducedWithArray(short[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref short p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("int")]
public int SumIntsArray()
{
return SumIntsWithArray(_arrayInts);
}
[Benchmark, BenchmarkCategory("int")]
public int SumIntsSpan()
{
return SumIntsWithSpan(_arrayInts);
}
[Benchmark, BenchmarkCategory("int")]
public int SumIntsArrayStrengthReduced()
{
return SumIntsStrengthReducedWithArray(_arrayInts);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumIntsWithArray(int[] input)
{
int result = 0;
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumIntsWithSpan(ReadOnlySpan<int> input)
{
int result = 0;
foreach (int s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumIntsStrengthReducedWithArray(int[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref int p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("long")]
public long SumLongsArray()
{
return SumLongsWithArray(_arrayLongs);
}
[Benchmark, BenchmarkCategory("long")]
public long SumLongsSpan()
{
return SumLongsWithSpan(_arrayLongs);
}
[Benchmark, BenchmarkCategory("long")]
public long SumLongsArrayStrengthReduced()
{
return SumLongsStrengthReducedWithArray(_arrayLongs);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumLongsWithArray(long[] input)
{
long result = 0;
foreach (long s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumLongsWithSpan(ReadOnlySpan<long> input)
{
int result = 0;
foreach (int s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumLongsStrengthReducedWithArray(long[] input)
{
long result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref long p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S3")]
public int SumS3Array()
{
return SumS3WithArray(_arrayS3);
}
[Benchmark, BenchmarkCategory("S3")]
public int SumS3Span()
{
return SumS3WithSpan(_arrayS3);
}
[Benchmark, BenchmarkCategory("S3")]
public int SumS3ArrayStrengthReduced()
{
return SumS3StrengthReducedWithArray(_arrayS3);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS3WithArray(S3[] input)
{
int result = 0;
foreach (S3 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS3WithSpan(ReadOnlySpan<S3> input)
{
int result = 0;
foreach (S3 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS3StrengthReducedWithArray(S3[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S3 p = ref input[0];
do
{
S3 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S8")]
public int SumS8Array()
{
return SumS8WithArray(_arrayS8);
}
[Benchmark, BenchmarkCategory("S8")]
public int SumS8Span()
{
return SumS8WithSpan(_arrayS8);
}
[Benchmark, BenchmarkCategory("S8")]
public int SumS8ArrayStrengthReduced()
{
return SumS8StrengthReducedWithArray(_arrayS8);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS8WithArray(S8[] input)
{
int result = 0;
foreach (S8 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS8WithSpan(ReadOnlySpan<S8> input)
{
int result = 0;
foreach (S8 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS8StrengthReducedWithArray(S8[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S8 p = ref input[0];
do
{
S8 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S12")]
public int SumS12Array()
{
return SumS12WithArray(_arrayS12);
}
[Benchmark, BenchmarkCategory("S12")]
public int SumS12Span()
{
return SumS12WithSpan(_arrayS12);
}
[Benchmark, BenchmarkCategory("S12")]
public int SumS12ArrayStrengthReduced()
{
return SumS12StrengthReducedWithArray(_arrayS12);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS12WithArray(S12[] input)
{
int result = 0;
foreach (S12 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS12WithSpan(ReadOnlySpan<S12> input)
{
int result = 0;
foreach (S12 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS12StrengthReducedWithArray(S12[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S12 p = ref input[0];
do
{
S12 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S16")]
public long SumS16Array()
{
return SumS16WithArray(_arrayS16);
}
[Benchmark, BenchmarkCategory("S16")]
public long SumS16Span()
{
return SumS16WithSpan(_arrayS16);
}
[Benchmark, BenchmarkCategory("S16")]
public long SumS16ArrayStrengthReduced()
{
return SumS16StrengthReducedWithArray(_arrayS16);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumS16WithArray(S16[] input)
{
long result = 0;
foreach (S16 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumS16WithSpan(ReadOnlySpan<S16> input)
{
long result = 0;
foreach (S16 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumS16StrengthReducedWithArray(S16[] input)
{
long result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S16 p = ref input[0];
do
{
S16 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S29")]
public int SumS29Array()
{
int sum = 0;
//for (int i = 0; i < 100; i++)
sum += SumS29WithArray(_arrayS29);
return sum;
}
[Benchmark, BenchmarkCategory("S29")]
public int SumS29Span()
{
int sum = 0;
//for (int i = 0; i < 100; i++)
sum += SumS29WithSpan(_arrayS29);
return sum;
}
[Benchmark, BenchmarkCategory("S29")]
public int SumS29ArrayStrengthReduced()
{
int sum = 0;
//for (int i = 0; i < 100; i++)
sum += SumS29StrengthReducedWithArray(_arrayS29);
return sum;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS29WithArray(S29[] input)
{
int result = 0;
foreach (S29 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS29WithSpan(ReadOnlySpan<S29> input)
{
int result = 0;
foreach (S29 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS29StrengthReducedWithArray(S29[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S29 p = ref input[0];
do
{
S29 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
private struct S3
{
public byte A, B, C;
}
public struct S8
{
public int A, B;
}
public struct S12
{
public int A, B, C;
}
public struct S16
{
public long A, B;
}
[StructLayout(LayoutKind.Sequential, Size = 29)]
public struct S29
{
public byte A;
}
}
}
@EgorBot -intel -commit 57f870f909dbfad35142e5aaa6e681464de4f439 vs 82ce118743cbd8f8261b6fb38fe0b0ec08d2030b --disasm
// 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 System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Loops
{
[GroupBenchmarksBy(BenchmarkDotNet.Configs.BenchmarkLogicalGroupRule.ByCategory)]
public class StrengthReduction
{
private short[] _arrayShorts;
private int[] _arrayInts;
private long[] _arrayLongs;
private S3[] _arrayS3;
private S8[] _arrayS8;
private S12[] _arrayS12;
private S16[] _arrayS16;
private S29[] _arrayS29;
[GlobalSetup]
public void Setup()
{
_arrayShorts = Enumerable.Range(0, 10000).Select(i => (short)i).ToArray();
_arrayInts = Enumerable.Range(0, 10000).Select(i => i).ToArray();
_arrayLongs = Enumerable.Range(0, 10000).Select(i => (long)i).ToArray();
_arrayS3 = Enumerable.Range(0, 10000).Select(i => new S3 { A = (byte)i, B = (byte)i, C = (byte)i }).ToArray();
_arrayS8 = Enumerable.Range(0, 10000).Select(i => new S8 { A = i, B = i, }).ToArray();
_arrayS12 = Enumerable.Range(0, 10000).Select(i => new S12 { A = i, B = i, C = i, }).ToArray();
_arrayS16 = Enumerable.Range(0, 10000).Select(i => new S16 { A = i, B = i, }).ToArray();
_arrayS29 = Enumerable.Range(0, 10000).Select(i => new S29 { A = (byte)i, }).ToArray();
}
[Benchmark(Baseline = true), BenchmarkCategory("short")]
public int SumShortsArray()
{
return SumShortsWithArray(_arrayShorts);
}
[Benchmark, BenchmarkCategory("short")]
public int SumShortsSpan()
{
return SumShortsWithSpan(_arrayShorts);
}
[Benchmark, BenchmarkCategory("short")]
public int SumShortsArrayStrengthReduced()
{
return SumShortsStrengthReducedWithArray(_arrayShorts);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsWithArray(short[] input)
{
int result = 0;
// 'or' by 1 to make loop body slightly larger to work around
// https://github.com/dotnet/runtime/issues/104665
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsWithSpan(ReadOnlySpan<short> input)
{
int result = 0;
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsStrengthReducedWithArray(short[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref short p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("int")]
public int SumIntsArray()
{
return SumIntsWithArray(_arrayInts);
}
[Benchmark, BenchmarkCategory("int")]
public int SumIntsSpan()
{
return SumIntsWithSpan(_arrayInts);
}
[Benchmark, BenchmarkCategory("int")]
public int SumIntsArrayStrengthReduced()
{
return SumIntsStrengthReducedWithArray(_arrayInts);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumIntsWithArray(int[] input)
{
int result = 0;
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumIntsWithSpan(ReadOnlySpan<int> input)
{
int result = 0;
foreach (int s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumIntsStrengthReducedWithArray(int[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref int p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("long")]
public long SumLongsArray()
{
return SumLongsWithArray(_arrayLongs);
}
[Benchmark, BenchmarkCategory("long")]
public long SumLongsSpan()
{
return SumLongsWithSpan(_arrayLongs);
}
[Benchmark, BenchmarkCategory("long")]
public long SumLongsArrayStrengthReduced()
{
return SumLongsStrengthReducedWithArray(_arrayLongs);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumLongsWithArray(long[] input)
{
long result = 0;
foreach (long s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumLongsWithSpan(ReadOnlySpan<long> input)
{
int result = 0;
foreach (int s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumLongsStrengthReducedWithArray(long[] input)
{
long result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref long p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S3")]
public int SumS3Array()
{
return SumS3WithArray(_arrayS3);
}
[Benchmark, BenchmarkCategory("S3")]
public int SumS3Span()
{
return SumS3WithSpan(_arrayS3);
}
[Benchmark, BenchmarkCategory("S3")]
public int SumS3ArrayStrengthReduced()
{
return SumS3StrengthReducedWithArray(_arrayS3);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS3WithArray(S3[] input)
{
int result = 0;
foreach (S3 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS3WithSpan(ReadOnlySpan<S3> input)
{
int result = 0;
foreach (S3 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS3StrengthReducedWithArray(S3[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S3 p = ref input[0];
do
{
S3 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S8")]
public int SumS8Array()
{
return SumS8WithArray(_arrayS8);
}
[Benchmark, BenchmarkCategory("S8")]
public int SumS8Span()
{
return SumS8WithSpan(_arrayS8);
}
[Benchmark, BenchmarkCategory("S8")]
public int SumS8ArrayStrengthReduced()
{
return SumS8StrengthReducedWithArray(_arrayS8);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS8WithArray(S8[] input)
{
int result = 0;
foreach (S8 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS8WithSpan(ReadOnlySpan<S8> input)
{
int result = 0;
foreach (S8 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS8StrengthReducedWithArray(S8[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S8 p = ref input[0];
do
{
S8 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S12")]
public int SumS12Array()
{
return SumS12WithArray(_arrayS12);
}
[Benchmark, BenchmarkCategory("S12")]
public int SumS12Span()
{
return SumS12WithSpan(_arrayS12);
}
[Benchmark, BenchmarkCategory("S12")]
public int SumS12ArrayStrengthReduced()
{
return SumS12StrengthReducedWithArray(_arrayS12);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS12WithArray(S12[] input)
{
int result = 0;
foreach (S12 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS12WithSpan(ReadOnlySpan<S12> input)
{
int result = 0;
foreach (S12 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS12StrengthReducedWithArray(S12[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S12 p = ref input[0];
do
{
S12 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S16")]
public long SumS16Array()
{
return SumS16WithArray(_arrayS16);
}
[Benchmark, BenchmarkCategory("S16")]
public long SumS16Span()
{
return SumS16WithSpan(_arrayS16);
}
[Benchmark, BenchmarkCategory("S16")]
public long SumS16ArrayStrengthReduced()
{
return SumS16StrengthReducedWithArray(_arrayS16);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumS16WithArray(S16[] input)
{
long result = 0;
foreach (S16 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumS16WithSpan(ReadOnlySpan<S16> input)
{
long result = 0;
foreach (S16 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private long SumS16StrengthReducedWithArray(S16[] input)
{
long result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S16 p = ref input[0];
do
{
S16 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[Benchmark(Baseline = true), BenchmarkCategory("S29")]
public int SumS29Array()
{
int sum = 0;
//for (int i = 0; i < 100; i++)
sum += SumS29WithArray(_arrayS29);
return sum;
}
[Benchmark, BenchmarkCategory("S29")]
public int SumS29Span()
{
int sum = 0;
//for (int i = 0; i < 100; i++)
sum += SumS29WithSpan(_arrayS29);
return sum;
}
[Benchmark, BenchmarkCategory("S29")]
public int SumS29ArrayStrengthReduced()
{
int sum = 0;
//for (int i = 0; i < 100; i++)
sum += SumS29StrengthReducedWithArray(_arrayS29);
return sum;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS29WithArray(S29[] input)
{
int result = 0;
foreach (S29 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS29WithSpan(ReadOnlySpan<S29> input)
{
int result = 0;
foreach (S29 s in input)
result += s.A | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumS29StrengthReducedWithArray(S29[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref S29 p = ref input[0];
do
{
S29 s = p;
result += s.A | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
private struct S3
{
public byte A, B, C;
}
public struct S8
{
public int A, B;
}
public struct S12
{
public int A, B, C;
}
public struct S16
{
public long A, B;
}
[StructLayout(LayoutKind.Sequential, Size = 29)]
public struct S29
{
public byte A;
}
}
}
@EgorBot -intel -commit 57f870f909dbfad35142e5aaa6e681464de4f439 vs 82ce118743cbd8f8261b6fb38fe0b0ec08d2030b --disasm
// 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 System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Loops
{
[GroupBenchmarksBy(BenchmarkDotNet.Configs.BenchmarkLogicalGroupRule.ByCategory)]
public class StrengthReduction
{
private short[] _arrayShorts;
[GlobalSetup]
public void Setup()
{
_arrayShorts = Enumerable.Range(0, 10000).Select(i => (short)i).ToArray();
}
[Benchmark(Baseline = true), BenchmarkCategory("short")]
public int SumShortsArray()
{
return SumShortsWithArray(_arrayShorts);
}
[Benchmark, BenchmarkCategory("short")]
public int SumShortsSpan()
{
return SumShortsWithSpan(_arrayShorts);
}
[Benchmark, BenchmarkCategory("short")]
public int SumShortsArrayStrengthReduced()
{
return SumShortsStrengthReducedWithArray(_arrayShorts);
}
[Benchmark, BenchmarkCategory("short")]
public int SumShortsSpanStrengthReduced()
{
return SumShortsStrengthReducedWithSpan(_arrayShorts);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsWithArray(short[] input)
{
int result = 0;
// 'or' by 1 to make loop body slightly larger to work around
// https://github.com/dotnet/runtime/issues/104665
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsWithSpan(ReadOnlySpan<short> input)
{
int result = 0;
foreach (short s in input)
result += s | 1;
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsStrengthReducedWithArray(short[] input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
ref short p = ref input[0];
do
{
result += p | 1;
p = ref Unsafe.Add(ref p, 1);
length--;
} while (length != 0);
}
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int SumShortsStrengthReducedWithSpan(ReadOnlySpan<short> input)
{
int result = 0;
uint length = (uint)input.Length;
if (length > 0)
{
nuint offset = 0;
ref short p = ref MemoryMarshal.GetReference(input);
do
{
result += Unsafe.AddByteOffset(ref p, offset) | 1;
offset += 2;
length--;
} while (length != 0);
}
return result;
}
}
}
Now that we have an SSA based IV analysis (added in #97865) we should implement strength reduction based on it. Example loop:
Codegen x64:
Codegen arm64:
The point of strength reduction is to optimize the loop codegen as if it had been written as follows:
The codegen would look like: x64:
arm64:
For arm64 there is the additional possibility of using post-increment addressing mode by optimizing the placement of the IV increment once the strength reduction has happened. The loop body is then reducible to: