龙空技术网

在.Net 5 GC新增的API

秋风技术 129

前言:

今天大家对“netnew数组”都比较重视,同学们都想要剖析一些“netnew数组”的相关内容。那么小编在网上汇集了一些关于“netnew数组””的相关内容,希望姐妹们能喜欢,朋友们一起来学习一下吧!

起因

在.Net 5中,在GC的公开方法中,增加了几个新的API.这几个API可能在工作用不到.这里也是学习一下.

AllocateArray 在托管堆根据长度分配数组,对数组对象分配默认值AllocateUninitializedArray 在托管堆根据长度分配数组,不对对象的属性分配默认值GetAllocatedBytesForCurrentThread 线程的生存期内在托管堆上分配的总字节数

在.Net 5 GC新增的API

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));}
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));}
 //MethodImplOptions表明AllocateNewArray是在CLR内部实现[MethodImpl(MethodImplOptions.InternalCall)]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

[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  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;        //重点还是AllocateNewArray,下面我们便去扒扒源码是如何实现的        return Unsafe.As<T[]>(AllocateNewArray(typeof(T[]).TypeHandle.Value, length, flags));     }}
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文件找到了.内部涉及的东西比较多.这里先不多源码进行解析,等后面有经验在来分析.

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))        {                      _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())            {                              _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

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

如果您觉得对您有用的话,可以点个赞或者加个关注,欢迎大家一起进行技术交流

标签: #netnew数组