【发布时间】:2020-08-26 22:09:53
【问题描述】:
在 .Net Core 3.1 上,我有许多大型 2D 数组,我需要对数组中单行的切片进行操作。同一个切片可能被多个操作使用,所以我想只执行一次切片并重用切片。
下面的示例代码对一个数组进行切片,然后调用2个函数对切片进行操作。
public void MyFunc()
{
double[,] array = ...; // populate the array
// select which part of the array to slice, values not important
int index0 = 0;
int startIndex1 = 1;
int sliceLength = 2;
// slice the array
ReadOnlySpan<double> slice = Slice(array, index0, startIndex1, sliceLength);
// do things with the slice
DoSomething1(slice);
DoSomething2(slice);
}
public unsafe ReadOnlySpan<double> Slice(double[,] array, int index0, int startIndex1, int sliceLength)
{
int arrayLength = array.GetLength(0) * array.GetLength(1);
int arrayStartIndex = index0 * array.GetLength(1) + startIndex1;
ReadOnlySpan<double> slice;
fixed (double* arrayPtr = array)
{
slice = new ReadOnlySpan<double>(arrayPtr, arrayLength).Slice(arrayStartIndex, sliceLength);
}
// does it matter if slice is returned inside or outside of the fixed block?
return slice;
}
public void DoSomething1(ReadOnlySpan<double> slice)
{
...
}
public void DoSomething2(ReadOnlySpan<double> slice)
{
...
}
“固定”确保 GC 在创建“切片”时不会移动“数组”。创建“slice”后,如果 GC 移动“array”,它会更新“slice”以引用新的“array”地址还是“slice”仍然引用旧地址?换句话说,DoSomething1(...) 和 DoSomething2(...) 是否总是对原始数组的预期切片进行操作,或者它们是否会无意中对随机内存块进行操作?
另外,“返回切片”是否重要?是在“固定”块的内部还是外部?
编辑 在https://stackoverflow.com/a/40589439/13532170 的启发下,我设法编写了一个测试来证明 V0ldek 关于 GC 在移动父数组时更新 ReadOnlySpan 的地址是正确的。
public static unsafe void ReadOnlySpanTest()
{
// create 2D array
double[,] array = new double[,] { {1, 2, 3}, {4, 5, 6} };
// parameters to convert 2D array to 1D span
int arrayLength = array.GetLength(0) * array.GetLength(1);
int sliceStartIndex = 1;
int sliceLength = 2;
// create span
IntPtr arrayAddressBeforeMove;
ReadOnlySpan<double> spanFromPointer;
fixed (double* arrayPtr = array)
{
arrayAddressBeforeMove = (IntPtr)arrayPtr;
// spanFromPointer should contain { 2, 3 }
spanFromPointer = new ReadOnlySpan<double>(arrayPtr, arrayLength).Slice(sliceStartIndex, sliceLength);
}
// trick GC into moving the array
GC.AddMemoryPressure(10000000);
GC.Collect();
GC.RemoveMemoryPressure(10000000);
// check array address and span contents again
IntPtr arrayAddressAfterMove;
fixed (double* arrayPtr = array)
{
// arrayAddressAfterMove should be different from arrayAddressBeforeMove
arrayAddressAfterMove = (IntPtr) arrayPtr;
// spanFromPointer should still contain { 2, 3 }
}
}
在调试器中跨过 ReadOnlySpanTest(),我可以看到 arrayAddressAfterMove != arrayAddressBeforeMove,表明 GC 确实移动了我的数组。我还可以看到 spanFromPointer 在数组移动之前和之后都包含 { 2, 3 } 。所以不管 ReadOnlySpan 是用“固定”块创建的,离开“固定”块后仍然可以安全使用。
【问题讨论】:
标签: c# .net-core garbage-collection unsafe-pointers