.Net GC新增的API
起因
在.Net 5中,在GC的公开方法中,增加了几个新的API.这几个API可能在工作用不到.这里也是学习一下.- AllocateArray 在托管堆根据长度分配数组,对数组对象分配默认值
- AllocateUninitializedArray 在托管堆根据长度分配数组,不对对象的属性分配默认值
- GetAllocatedBytesForCurrentThread 线程的生存期内在托管堆上分配的总字节数

1. 在项目中经常使用的方式,分配数组
public void ArrayTest()
{
People[] peoples = new People[16];
Console.WriteLine(peoples.Length);
}
2. 在托管堆分配数组AllocateArray
public void AllocateArrayTest()
{
//从.Net 5开始支持
//AllocateArray 在托管堆根据长度分配数组,对数组对象分配默认值
//第一个参数是需要分配数组的长度
//第二个参数是否在GC中固定
People[] arr = GC.AllocateArray<People>(16);
Console.WriteLine(arr.Length);
}
AllocateArray源码在src/coreclr/System.Private.CoreLib/src/System/GC.cs
public static T[] AllocateArray<T>(int length, bool pinned = false) // T[] rather than T?[] to match `new T[length]` behavior
{
GC_ALLOC_FLAGS flags = GC_ALLOC_FLAGS.GC_ALLOC_NO_FLAGS;
if (pinned)
{
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
flags = GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP;
}
return Unsafe.As<T[]>(AllocateNewArray(typeof(T[]).TypeHandle.Value, length, flags));
}
/// <summary>
/// Allocate an array.
/// </summary>
/// <typeparam name="T">Specifies the type of the array element.</typeparam>
/// <param name="length">Specifies the length of the array.</param>
/// <param name="pinned">Specifies whether the allocated array must be pinned.</param>
/// <remarks>
/// If pinned is set to true, <typeparamref name="T"/> must not be a reference type or a type that contains object references.
/// </remarks>
public static T[] AllocateArray<T>(int length, bool pinned = false) // T[] rather than T?[] to match `new T[length]` behavior
{
GC_ALLOC_FLAGS flags = GC_ALLOC_FLAGS.GC_ALLOC_NO_FLAGS;
if (pinned)
{
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
flags = GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP; //是否需要在托管堆上固定
}
//内部使用AllocateNewArray,这个API并不是c#实现
return Unsafe.As<T[]>(AllocateNewArray(typeof(T[]).TypeHandle.Value, length, flags));
}
[MethodImpl(MethodImplOptions.InternalCall)] //表明AllocateNewArray是在CLR内部实现
internal static extern Array AllocateNewArray(IntPtr typeHandle, int length, GC_ALLOC_FLAGS flags);
AllocateArray内部调用AllocateNewArray,具体实现是在CLR中实现.后面我们去跟AllocateNewArray源码实现.
3. AllocateUninitializedArray在托管堆上分配数组
public void AllocateUninitializedArrayTest()
{
//从.Net 5开始支持
//AllocateUninitializedArray 在托管堆根据长度分配数组,不对对象的属性分配默认值
//第一个参数是需要分配数组的长度
//第二个参数是否在GC中固定
People[] arr = GC.AllocateUninitializedArray<People>(16);
Console.WriteLine(arr.Length);
}
AllocateUninitializedArray源码在src/coreclr/System.Private.CoreLib/src/System/GC.cs
/// <summary>
/// Allocate an array while skipping zero-initialization if possible.
/// </summary>
/// <typeparam name="T">Specifies the type of the array element.</typeparam>
/// <param name="length">Specifies the length of the array.</param>
/// <param name="pinned">Specifies whether the allocated array must be pinned.</param>
/// <remarks>
/// If pinned is set to true, <typeparamref name="T"/> must not be a reference type or a type that contains object references.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // forced to ensure no perf drop for small memory buffers (hot path)
public static T[] AllocateUninitializedArray<T>(int length, bool pinned = false) // T[] rather than T?[] to match `new T[length]` behavior
{
if (!pinned) //不固定在堆上固定对象
{
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) //如果是引用类型,直接根据类型使用直接用分组的方式
{
return new T[length];
}
// for debug builds we always want to call AllocateNewArray to detect AllocateNewArray bugs
#if !DEBUG
// small arrays are allocated using `new[]` as that is generally faster.
if (length < 2048 / Unsafe.SizeOf<T>()) //数组长度较小的情况,也是使用直接的方式在堆上分配数组
{
return new T[length];
}
#endif
}
else if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) //如果该类型是引用类型并包含其他引用类型,会抛出异常
{
ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T));
}
// kept outside of the small arrays hot path to have inlining without big size growth
return AllocateNewUninitializedArray(length, pinned);
// remove the local function when https://github.com/dotnet/runtime/issues/5973 is implemented
static T[] AllocateNewUninitializedArray(int length, bool pinned)
{
GC_ALLOC_FLAGS flags = GC_ALLOC_FLAGS.GC_ALLOC_ZEROING_OPTIONAL;
if (pinned)
flags |= GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP;
return Unsafe.As<T[]>(AllocateNewArray(typeof(T[]).TypeHandle.Value, length, flags)); //重点还是AllocateNewArray,下面我们便去扒扒源码是如何实现的
}
}
4. AllocateNewArray 在CLR是如何实现的
在.Net 看到MethodImplOptions.InternalCall标记的方法 在CoreCLR中一般可以先去src/coreclr/vm/ecalllist.h查找
在Mono中一般可以先去src/mono/mono/metadata/icall.c查找
在ecalllist.h文件中可以看到:
FCFuncElement("AllocateNewArray", GCInterface::AllocateNewArray)
根据GCInterface找不到对应cpp文件和头文件.如果用VS打开的话,可以根据解决方案中查找.
不过最后在src/coreclr/vm/comutilnative.cpp文件找到了.内部涉及的东西比较多.这里先不多源码进行解析,等后面有经验在来分析.
/*===============================AllocateNewArray===============================
**Action: Allocates a new array object. Allows passing extra flags
**Returns: The allocated array.
**Arguments: elementTypeHandle -> type of the element,
** length -> number of elements,
** zeroingOptional -> whether caller prefers to skip clearing the content of the array, if possible.
**Exceptions: IDS_EE_ARRAY_DIMENSIONS_EXCEEDED when size is too large. OOM if can't allocate.
==============================================================================*/
FCIMPL3(Object*, GCInterface::AllocateNewArray, void* arrayTypeHandle, INT32 length, INT32 flags)
{
CONTRACTL {
FCALL_CHECK;
} CONTRACTL_END;
OBJECTREF pRet = NULL;
TypeHandle arrayType = TypeHandle::FromPtr(arrayTypeHandle);
HELPER_METHOD_FRAME_BEGIN_RET_0();
//Only the following flags are used by GC.cs, so we'll just assert it here.
_ASSERTE((flags & ~(GC_ALLOC_ZEROING_OPTIONAL | GC_ALLOC_PINNED_OBJECT_HEAP)) == 0);
pRet = AllocateSzArray(arrayType, length, (GC_ALLOC_FLAGS)flags); //这里
HELPER_METHOD_FRAME_END();
return OBJECTREFToObject(pRet);
}
FCIMPLEND
OBJECTREF AllocateSzArray(TypeHandle arrayType, INT32 cElements, GC_ALLOC_FLAGS flags)
{
CONTRACTL{
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE; // returns an objref without pinning it => cooperative
} CONTRACTL_END;
MethodTable* pArrayMT = arrayType.AsMethodTable();
return AllocateSzArray(pArrayMT, cElements, flags);
}
OBJECTREF AllocateSzArray(MethodTable* pArrayMT, INT32 cElements, GC_ALLOC_FLAGS flags)
{
CONTRACTL{
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE; // returns an objref without pinning it => cooperative
} CONTRACTL_END;
// IBC Log MethodTable access
g_IBCLogger.LogMethodTableAccess(pArrayMT);
SetTypeHandleOnThreadForAlloc(TypeHandle(pArrayMT));
_ASSERTE(pArrayMT->CheckInstanceActivated());
_ASSERTE(pArrayMT->GetInternalCorElementType() == ELEMENT_TYPE_SZARRAY);
CorElementType elemType = pArrayMT->GetArrayElementType();
// Disallow the creation of void[] (an array of System.Void)
if (elemType == ELEMENT_TYPE_VOID)
COMPlusThrow(kArgumentException);
if (cElements < 0)
COMPlusThrow(kOverflowException);
if ((SIZE_T)cElements > MaxArrayLength())
ThrowOutOfMemoryDimensionsExceeded();
// Allocate the space from the GC heap
SIZE_T componentSize = pArrayMT->GetComponentSize();
#ifdef TARGET_64BIT
// POSITIVE_INT32 * UINT16 + SMALL_CONST
// this cannot overflow on 64bit
size_t totalSize = cElements * componentSize + pArrayMT->GetBaseSize();
#else
S_SIZE_T safeTotalSize = S_SIZE_T((DWORD)cElements) * S_SIZE_T((DWORD)componentSize) + S_SIZE_T((DWORD)pArrayMT->GetBaseSize());
if (safeTotalSize.IsOverflow())
ThrowOutOfMemoryDimensionsExceeded();
size_t totalSize = safeTotalSize.Value();
#endif
#ifdef FEATURE_DOUBLE_ALIGNMENT_HINT
if ((elemType == ELEMENT_TYPE_R8) &&
((DWORD)cElements >= g_pConfig->GetDoubleArrayToLargeObjectHeapThreshold()))
{
STRESS_LOG2(LF_GC, LL_INFO10, "Allocating double MD array of size %d and length %d to large object heap\n", totalSize, cElements);
flags |= GC_ALLOC_LARGE_OBJECT_HEAP;
}
#endif
if (totalSize >= g_pConfig->GetGCLOHThreshold())
flags |= GC_ALLOC_LARGE_OBJECT_HEAP;
if (pArrayMT->ContainsPointers())
flags |= GC_ALLOC_CONTAINS_REF;
ArrayBase* orArray = NULL;
if (flags & GC_ALLOC_USER_OLD_HEAP) //如果还在原先堆上
{
orArray = (ArrayBase*)Alloc(totalSize, flags); //在这里分配内存空间,Alloc内部是调用malloc
orArray->SetMethodTableForUOHObject(pArrayMT);
}
else
{
#ifndef FEATURE_64BIT_ALIGNMENT
if ((DATA_ALIGNMENT < sizeof(double)) && (elemType == ELEMENT_TYPE_R8) &&
(totalSize < g_pConfig->GetGCLOHThreshold() - MIN_OBJECT_SIZE))
{
// Creation of an array of doubles, not in the large object heap.
// We want to align the doubles to 8 byte boundaries, but the GC gives us pointers aligned
// to 4 bytes only (on 32 bit platforms). To align, we ask for 12 bytes more to fill with a
// dummy object.
// If the GC gives us a 8 byte aligned address, we use it for the array and place the dummy
// object after the array, otherwise we put the dummy object first, shifting the base of
// the array to an 8 byte aligned address.
//
// Note: on 64 bit platforms, the GC always returns 8 byte aligned addresses, and we don't
// execute this code because DATA_ALIGNMENT < sizeof(double) is false.
_ASSERTE(DATA_ALIGNMENT == sizeof(double) / 2);
_ASSERTE((MIN_OBJECT_SIZE % sizeof(double)) == DATA_ALIGNMENT); // used to change alignment
_ASSERTE(pArrayMT->GetComponentSize() == sizeof(double));
_ASSERTE(g_pObjectClass->GetBaseSize() == MIN_OBJECT_SIZE);
_ASSERTE(totalSize < totalSize + MIN_OBJECT_SIZE);
orArray = (ArrayBase*)Alloc(totalSize + MIN_OBJECT_SIZE, flags); //进行内存分配
Object* orDummyObject;
if ((size_t)orArray % sizeof(double))
{
orDummyObject = orArray;
orArray = (ArrayBase*)((size_t)orArray + MIN_OBJECT_SIZE);
}
else
{
orDummyObject = (Object*)((size_t)orArray + totalSize);
}
_ASSERTE(((size_t)orArray % sizeof(double)) == 0);
orDummyObject->SetMethodTable(g_pObjectClass);
}
else
#endif // FEATURE_64BIT_ALIGNMENT
{
#ifdef FEATURE_64BIT_ALIGNMENT
MethodTable* pElementMT = pArrayMT->GetArrayElementTypeHandle().GetMethodTable();
if (pElementMT->RequiresAlign8() && pElementMT->IsValueType())
{
// This platform requires that certain fields are 8-byte aligned (and the runtime doesn't provide
// this guarantee implicitly, e.g. on 32-bit platforms). Since it's the array payload, not the
// header that requires alignment we need to be careful. However it just so happens that all the
// cases we care about (single and multi-dim arrays of value types) have an even number of DWORDs
// in their headers so the alignment requirements for the header and the payload are the same.
_ASSERTE(((pArrayMT->GetBaseSize() - SIZEOF_OBJHEADER) & 7) == 0);
flags |= GC_ALLOC_ALIGN8;
}
#endif
orArray = (ArrayBase*)Alloc(totalSize, flags); //进行内存分配
}
orArray->SetMethodTable(pArrayMT);
}
// Initialize Object
orArray->m_NumComponents = cElements;
PublishObjectAndNotify(orArray, flags);
return ObjectToOBJECTREF((Object*)orArray);
}
5. GetAllocatedBytesForCurrentThread 线程的生存期内在托管堆上分配的总字节数
public void GetAllocatedBytesForCurrentThreadTest()
{
long currentThreadAllocateBytes = GC.GetAllocatedBytesForCurrentThread(); //线程的生存期内在托管堆上分配的总字节数
Console.WriteLine(currentThreadAllocateBytes);
}
GetAllocatedBytesForCurrentThread源码也是在CoreCLR实现.
在src/coreclr/vm/comutilnative.cpp
/*===============================GetAllocatedBytesForCurrentThread===============================
**Action: Computes the allocated bytes so far on the current thread
**Returns: The allocated bytes so far on the current thread
**Arguments: None
**Exceptions: None
==============================================================================*/
FCIMPL0(INT64, GCInterface::GetAllocatedBytesForCurrentThread)
{
FCALL_CONTRACT;
INT64 currentAllocated = 0;
Thread *pThread = GetThread(); //获取当前线程
gc_alloc_context* ac = pThread->GetAllocContext(); //获取当前线程的内存分配的上下文
currentAllocated = ac->alloc_bytes + ac->alloc_bytes_uoh - (ac->alloc_limit - ac->alloc_ptr);
return currentAllocated;
}
FCIMPLEND
秋风
2021-07-18