Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
Improve MemoryMarshal.Cast
Browse files Browse the repository at this point in the history
Avoid unnecessary checked and signed arithmetic. Handle special cases such as cast between same size types and from byte sized types, the JIT is unable to optimize these cases currently.
  • Loading branch information
mikedn committed Feb 28, 2018
1 parent 08c87c5 commit e8fa5a8
Showing 1 changed file with 62 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public static Memory<T> AsMemory<T>(ReadOnlyMemory<T> readOnlyMemory) =>
/// <exception cref="System.ArgumentException">
/// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<TTo> Cast<TFrom, TTo>(Span<TFrom> source)
where TFrom : struct
where TTo : struct
Expand All @@ -59,9 +60,38 @@ public static Span<TTo> Cast<TFrom, TTo>(Span<TFrom> source)
if (RuntimeHelpers.IsReferenceOrContainsReferences<TTo>())
ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo));

int newLength;
// Use unsigned integers - unsigned division by constant (especially by power of 2)
// and checked casts are faster and smaller.
uint fromSize = (uint)Unsafe.SizeOf<TFrom>();
uint toSize = (uint)Unsafe.SizeOf<TTo>();
uint length = (uint)source.Length;
if (fromSize == toSize)
{
// Special case for same size types - `(ulong)length * (ulong)fromSize / (ulong)toSize`
// should be optimized to just `length` but the JIT doesn't do that today.
newLength = (int)length;
}
else if (fromSize == 1)
{
// Special case for byte sized TFrom - `(ulong)length * (ulong)fromSize / (ulong)toSize`
// becomes `(ulong)length / (ulong)toSize` but the JIT can't narrow it down to `int`
// and can't eliminate the checked cast. This also avoids a 32 bit specific issue,
// the JIT can't eliminate long multiply by 1.
newLength = (int)(length / toSize);
}
else
{
// Ensure that casts are done in such a way that the JIT is able to "see"
// the uint->ulong casts and the multiply together so that on 32 bit targets
// 32x32to64 multiplication is used.
ulong newUInt64Length = (ulong)length * (ulong)fromSize / (ulong)toSize;
newLength = checked((int)newUInt64Length);
}

return new Span<TTo>(
ref Unsafe.As<TFrom, TTo>(ref source._pointer.Value),
checked((int)((long)source.Length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>())));
newLength);
}

/// <summary>
Expand All @@ -75,6 +105,7 @@ ref Unsafe.As<TFrom, TTo>(ref source._pointer.Value),
/// <exception cref="System.ArgumentException">
/// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> source)
where TFrom : struct
where TTo : struct
Expand All @@ -84,9 +115,38 @@ public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> source)
if (RuntimeHelpers.IsReferenceOrContainsReferences<TTo>())
ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo));

int newLength;
// Use unsigned integers - unsigned division by constant (especially by power of 2)
// and checked casts are faster and smaller.
uint fromSize = (uint)Unsafe.SizeOf<TFrom>();
uint toSize = (uint)Unsafe.SizeOf<TTo>();
uint length = (uint)source.Length;
if (fromSize == toSize)
{
// Special case for same size types - `(ulong)length * (ulong)fromSize / (ulong)toSize`
// should be optimized to just `length` but the JIT doesn't do that today.
newLength = (int)length;
}
else if (fromSize == 1)
{
// Special case for byte sized TFrom - `(ulong)length * (ulong)fromSize / (ulong)toSize`
// becomes `(ulong)length / (ulong)toSize` but the JIT can't narrow it down to `int`
// and can't eliminate the checked cast. This also avoids a 32 bit specific issue,
// the JIT can't eliminate long multiply by 1.
newLength = (int)(length / toSize);
}
else
{
// Ensure that casts are done in such a way that the JIT is able to "see"
// the uint->ulong casts and the multiply together so that on 32 bit targets
// 32x32to64 multiplication is used.
ulong newUInt64Length = (ulong)length * (ulong)fromSize / (ulong)toSize;
newLength = checked((int)newUInt64Length);
}

return new ReadOnlySpan<TTo>(
ref Unsafe.As<TFrom, TTo>(ref MemoryMarshal.GetReference(source)),
checked((int)((long)source.Length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>())));
newLength);
}

/// <summary>
Expand Down

0 comments on commit e8fa5a8

Please sign in to comment.