获得C#中的分配总数

问题描述 投票:2回答:3

是否有一种方法可以获取分配的总数(注意-分配数量,而不是分配的字节数?它可以是当前线程,也可以是全局线程,以较容易的一个为准。

[我想检查一个特定函数分配了多少个对象,虽然我知道调试-> Performance Profiler(Alt + F2),但我希望能够从程序内部以编程方式进行操作。

// pseudocode
int GetTotalAllocations() {
    ...;
}    
class Foo {
    string bar;
    string baz;
}
public static void Main() {
    int allocationsBefore = GetTotalAllocations();
    PauseGarbageCollector(); // do I need this? I don't want the GC to run during the function and skew the number of allocations
    // Some code that makes allocations.
    var foo = new Foo() { bar = "bar", baz = "baz" };
    ResumeGarbageCollector();
    int allocationsAfter = GetTotalAllocations();
    Console.WriteLine(allocationsAfter - allocationsBefore); // Should print 3 allocations - one for Foo, and 2 for its fields.
}

此外,我是否需要暂停垃圾收集以获取准确的数据,我可以这样做吗?

我需要使用CLR分析API来实现吗?

c# allocation etw .net-core-3.1
3个回答
0
投票

[您需要使用一些kernel32函数,但是有可能!! :)我没有编写完整的代码,但是希望您能感觉应该怎么做。

首先,您需要使用功能进行所有处理:Process.GetProcesseslink那么您需要从中创建快照CreateToolhelp32Snapshot,因为此快照不需要“ GC暂停”,并且需要创建循环以枚举所有内存块之后。使用Heap32ListFirstHeap32First初始化循环功能,然后可以调用Heap32Next直到成功。

并且当它在您的代码中这样声明时,您可以调用kerner32函数:

[DllImport("kernel32", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
static extern IntPtr CreateToolhelp32Snapshot([In]UInt32 dwFlags, [In]UInt32 th32ProcessID);

这里是c ++示例,但是您可以在CSharp函数声明之后执行相同的操作:Traversing the Heap List

我知道这并不容易,但是没有简单的方法。顺便说一下,如果您在循环内调用Toolhelp32ReadProcessMemory,则可以检索许多有用的其他信息。


而且我发现pinvoke.net可能对您有帮助pinvoke.net

https://www.pinvoke.net/default.aspx/kernel32.createtoolhelp32snapshothttps://www.pinvoke.net/default.aspx/kernel32.Heap32ListFirst


0
投票

至少在当前情况下,您要问的是不可能的,让我们看看为什么。

有没有办法获得分配总数

是的,有可能。但是我假设您认为每次使用new运算符创建的对象都会在堆上发生分配。那不是真的堆是按段分配的。在填满最后一个分配的段之前,GC可能会分配几个new操作员调用,然后GC决定分配一个新的。enter image description here

此图说明了这种情况:堆中有几个段,但是它们被不同的对象填充。

enter image description here

因此有一种方法可以获取分配的段数,但是它们不对应于对象数,它们的大小也可以不同;第一个段可以为100个字节,另一个段可以为800个字节。

我要检查特定功能分配了多少个对象

您可以在给定的时刻获得多少个对象。无法将信息缩小到特定功能。您可以假设,如果您两次计算堆中有多少个对象,一次是在函数调用之前,一次是在函数调用之后,那么您将能够从另一个中减去一个值并获得差值。

这里的问题是,可以在应用程序中的这两次测量之间分配数百万个对象,因此您的函数分配将显得微不足道,无法获得真实的画面并将函数分配与所有其他对象区分开。

我编写了代码来说明这些方法,您可以找到它here。我使用EnumerateObjectAddresses获取堆上对象的地址,并使用ETW的TraceEvenSession.Source.Clr.GCAllocationTick获取有关段分配的事件。


0
投票

首先,您可以通过调用System.GC.TryStartNoGCRegion暂停GC,并用System.GC.TryStartNoGCRegion取消暂停它。

仅知道分配了多少个bytes,有System.GC.EndNoGCRegion返回为当前线程分配的总字节数。在要测量的代码之前和之后调用它,区别是分配大小。

计算分配数]有点棘手。可能有很多方法可以实现这些功能,而今天它们在某种程度上都是次优的。我可以想到一个主意:

修改默认GC

从.NET Core 2.1开始,可以使用自定义GC,即所谓的local GC。据说开发经验,文档和实用性不是最好的,但是根据问题的具体情况,它可能对您有所帮助。

每次分配对象时,运行时调用System.GC.EndNoGCRegionSystem.GC.GetAllocatedBytesForCurrentThread是使用默认GC实现System.GC.GetAllocatedBytesForCurrentThread(在37292行中实现的GCHeap :: Alloc)定义的Object* IGCHeap::Alloc(gc_alloc_context * acontext, size_t size, uint32_t flags)

与该人交谈的人将是IGCHeap,其中有关于该主题的两个演示文稿:herehere

我们可以直接使用默认的GC实现,并修改Konrad Kokosa方法以在每次调用时增加一个计数器。

以托管代码公开计数器

接下来要使用新计数器,我们需要一种从托管代码中使用它的方法。为此,我们需要修改运行时。在这里,我将介绍如何通过扩展GC接口(由#1公开)来实现此目的。

注意:我没有这样做的实践经验,在走这条路线时可能会遇到一些问题。我只是想对自己的想法保持精确。

通过查看#2,我们能够找到如何添加导致内部CLR调用的方法。

打开Alloc并声明一个新方法:

System.GC

接下来,我们需要在本机GCInterface上定义该方法。为此,转到ulong GC.GetGenerationSize(int)并添加:

\runtime\src\coreclr\src\System.Private.CoreLib\src\System\GC.cs#112

要链接这两种方法,我们需要在[MethodImpl(MethodImplOptions.InternalCall)] internal static extern ulong GetAllocationCount(); 中列出它们:

runtime\src\coreclr\src\vm\comutilnative.h#112

最后,实际上是在static FCDECL0(UINT64, GetAllocationCount); 处实现该方法:

runtime\src\coreclr\src\vm\ecalllist.h#745

这将指向我们分配计数器所在的GCHeap的指针。尚未公开此方法的方法FCFuncElement("GetAllocationCount", GCInterface::GetAllocationCount) ,所以让我们创建它:

runtime\src\coreclr\src\vm\comutilnative.cpp#938

FCIMPL0(UINT64, GCInterface::GetAllocationCount)
{
    FCALL_CONTRACT;

    return (UINT64)(GCHeapUtilities::GetGCHeap()->GetAllocationCount());
}
FCIMPLEND

GetAllocationCount

runtime\src\coreclr\src\gc\gcimpl.h#313

size_t GetAllocationCount();

runtime\src\coreclr\src\gc\gcinterface.h#680

为了使新方法virtual size_t GetAllocationCount() = 0; 在托管代码中可用,我们需要针对自定义BCL进行编译。也许自定义NuGet包也可以在这里工作(如上所示,该包将runtime\src\coreclr\src\gc\gcee.cpp#239定义为内部调用)。

结束

诚然,如果以前没有做过,这将是很多工作,并且自定义GC + CLR在这里可能有点过大,但是我认为我应该把它扔掉。

而且,我还没有测试。您应该将其视为一个概念。

© www.soinside.com 2019 - 2024. All rights reserved.