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

Rewrite of Array.Copy fast path in C# #27634

Merged
merged 9 commits into from
Nov 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 112 additions & 9 deletions src/System.Private.CoreLib/src/System/Array.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,30 +135,133 @@ public static unsafe Array CreateInstance(Type elementType, int[] lengths, int[]
// Copies length elements from sourceArray, starting at index 0, to
// destinationArray, starting at index 0.
//
public static void Copy(Array sourceArray, Array destinationArray, int length)
public static unsafe void Copy(Array sourceArray, Array destinationArray, int length)
{
if (sourceArray == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceArray);
if (destinationArray == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.destinationArray);

Copy(sourceArray, sourceArray.GetLowerBound(0), destinationArray, destinationArray.GetLowerBound(0), length, false);
MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray);
if (pMT == RuntimeHelpers.GetMethodTable(destinationArray) &&
!pMT->IsMultiDimensionalArray &&
(uint)length <= (uint)sourceArray.Length &&
(uint)length <= (uint)destinationArray.Length)
{
nuint byteCount = (uint)length * (nuint)pMT->ComponentSize;
ref byte src = ref sourceArray.GetRawSzArrayData();
ref byte dst = ref destinationArray.GetRawSzArrayData();

if (pMT->ContainsGCPointers)
Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount);
jkotas marked this conversation as resolved.
Show resolved Hide resolved
else
Buffer.Memmove(ref dst, ref src, byteCount);

// GC.KeepAlive(sourceArray) not required. pMT kept alive via sourceArray
jkotas marked this conversation as resolved.
Show resolved Hide resolved
return;
}

// Less common
Copy(sourceArray, sourceArray.GetLowerBound(0), destinationArray, destinationArray.GetLowerBound(0), length, reliable: false);
}

// Copies length elements from sourceArray, starting at sourceIndex, to
// destinationArray, starting at destinationIndex.
//
public static void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
public static unsafe void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
{
if (sourceArray != null && destinationArray != null)
{
MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray);
if (pMT == RuntimeHelpers.GetMethodTable(destinationArray) &&
!pMT->IsMultiDimensionalArray &&
length >= 0 && sourceIndex >= 0 && destinationIndex >= 0 &&
(uint)(sourceIndex + length) <= (uint)sourceArray.Length &&
(uint)(destinationIndex + length) <= (uint)destinationArray.Length)
{
nuint elementSize = (nuint)pMT->ComponentSize;
nuint byteCount = (uint)length * elementSize;
ref byte src = ref Unsafe.AddByteOffset(ref sourceArray.GetRawSzArrayData(), (uint)sourceIndex * elementSize);
ref byte dst = ref Unsafe.AddByteOffset(ref destinationArray.GetRawSzArrayData(), (uint)destinationIndex * elementSize);

if (pMT->ContainsGCPointers)
Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount);
else
Buffer.Memmove(ref dst, ref src, byteCount);

// GC.KeepAlive(sourceArray) not required. pMT kept alive via sourceArray
return;
}
}

// Less common
Copy(sourceArray!, sourceIndex, destinationArray!, destinationIndex, length, reliable: false);
}

private static unsafe void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable)
{
Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length, false);
if (sourceArray == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceArray);
if (destinationArray == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.destinationArray);

if (sourceArray.GetType() != destinationArray.GetType() && sourceArray.Rank != destinationArray.Rank)
throw new RankException(SR.Rank_MustMatch);

if (length < 0)
throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);

int srcLB = sourceArray.GetLowerBound(0);
if (sourceIndex < srcLB || sourceIndex - srcLB < 0)
jkotas marked this conversation as resolved.
Show resolved Hide resolved
throw new ArgumentOutOfRangeException(nameof(sourceIndex), SR.ArgumentOutOfRange_ArrayLB);
sourceIndex -= srcLB;

int dstLB = destinationArray.GetLowerBound(0);
if (destinationIndex < dstLB || destinationIndex - dstLB < 0)
throw new ArgumentOutOfRangeException(nameof(destinationIndex), SR.ArgumentOutOfRange_ArrayLB);
destinationIndex -= dstLB;

if ((uint)(sourceIndex + length) > (uint)sourceArray.Length)
throw new ArgumentException(SR.Arg_LongerThanSrcArray, nameof(sourceArray));
if ((uint)(destinationIndex + length) > (uint)destinationArray.Length)
throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray));

if (sourceArray.GetType() == destinationArray.GetType() || IsSimpleCopy(sourceArray, destinationArray))
{
MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray);

nuint elementSize = (nuint)pMT->ComponentSize;
nuint byteCount = (uint)length * elementSize;
ref byte src = ref Unsafe.AddByteOffset(ref sourceArray.GetRawArrayData(), (uint)sourceIndex * elementSize);
ref byte dst = ref Unsafe.AddByteOffset(ref destinationArray.GetRawArrayData(), (uint)destinationIndex * elementSize);

if (pMT->ContainsGCPointers)
Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount);
else
Buffer.Memmove(ref dst, ref src, byteCount);

// GC.KeepAlive(sourceArray) not required. pMT kept alive via sourceArray
return;
}

// If we were called from Array.ConstrainedCopy, ensure that the array copy
// is guaranteed to succeed.
if (reliable)
throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_ConstrainedCopy);

// Rare
CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
jkotas marked this conversation as resolved.
Show resolved Hide resolved
}

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool IsSimpleCopy(Array sourceArray, Array destinationArray);

// Reliability-wise, this method will either possibly corrupt your
// instance & might fail when called from within a CER, or if the
// reliable flag is true, it will either always succeed or always
// throw an exception with no side effects.
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable);
private static extern void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length);

// Provides a strong exception guarantee - either it succeeds, or
// it throws an exception with no side effects. The arrays must be
Expand All @@ -167,7 +270,7 @@ public static void Copy(Array sourceArray, int sourceIndex, Array destinationArr
// It will up-cast, assuming the array types are correct.
public static void ConstrainedCopy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
{
Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length, true);
Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: true);
}

// Sets length elements in array to 0 (or null for Object arrays), starting
Expand All @@ -194,10 +297,10 @@ public static unsafe void Clear(Array array, int index, int length)
if (index < lowerBound || offset < 0 || length < 0 || (uint)(offset + length) > (uint)array.Length)
ThrowHelper.ThrowIndexOutOfRangeException();

uint elementSize = pMT->ComponentSize;
nuint elementSize = pMT->ComponentSize;

ref byte ptr = ref Unsafe.AddByteOffset(ref p, (uint)offset * (nuint)elementSize);
nuint byteLength = (uint)length * (nuint)elementSize;
ref byte ptr = ref Unsafe.AddByteOffset(ref p, (uint)offset * elementSize);
nuint byteLength = (uint)length * elementSize;

if (pMT->ContainsGCPointers)
SpanHelpers.ClearWithReferences(ref Unsafe.As<byte, IntPtr>(ref ptr), byteLength / (uint)sizeof(IntPtr));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ private static void PrelinkCore(MethodInfo m)
/// structure contains pointers to allocated blocks and "fDeleteOld" is
/// true, this routine will call DestroyStructure() first.
/// </summary>
[MethodImpl(MethodImplOptions.InternalCall), ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void StructureToPtr(object structure, IntPtr ptr, bool fDeleteOld);

private static object PtrToStructureHelper(IntPtr ptr, Type structureType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ private void SetCultureOnUnstartedThreadNoCheck(CultureInfo value, bool uiCultur
[MethodImpl(MethodImplOptions.NoInlining)]
private static Thread InitializeCurrentThread() => t_currentThread = GetCurrentThreadNative();

[MethodImpl(MethodImplOptions.InternalCall), ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern Thread GetCurrentThreadNative();

private void SetStartHelper(Delegate start, int maxStackSize)
Expand Down
Loading