是否有一种方法可以获取分配的总数(注意-分配数量,而不是分配的字节数?它可以是当前线程,也可以是全局线程,以较容易的一个为准。
[我想检查一个特定函数分配了多少个对象,虽然我知道调试-> 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来实现吗?
[您需要使用一些kernel32函数,但是有可能!! :)我没有编写完整的代码,但是希望您能感觉应该怎么做。
首先,您需要使用功能进行所有处理:Process.GetProcesses
link那么您需要从中创建快照CreateToolhelp32Snapshot
,因为此快照不需要“ GC暂停”,并且需要创建循环以枚举所有内存块之后。使用Heap32ListFirst
和Heap32First
初始化循环功能,然后可以调用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
至少在当前情况下,您要问的是不可能的,让我们看看为什么。
有没有办法获得分配总数
是的,有可能。但是我假设您认为每次使用new
运算符创建的对象都会在堆上发生分配。那不是真的堆是按段分配的。在填满最后一个分配的段之前,GC可能会分配几个new
操作员调用,然后GC决定分配一个新的。
此图说明了这种情况:堆中有几个段,但是它们被不同的对象填充。
因此有一种方法可以获取分配的段数,但是它们不对应于对象数,它们的大小也可以不同;第一个段可以为100个字节,另一个段可以为800个字节。
我要检查特定功能分配了多少个对象
您可以在给定的时刻获得多少个对象。无法将信息缩小到特定功能。您可以假设,如果您两次计算堆中有多少个对象,一次是在函数调用之前,一次是在函数调用之后,那么您将能够从另一个中减去一个值并获得差值。
这里的问题是,可以在应用程序中的这两次测量之间分配数百万个对象,因此您的函数分配将显得微不足道,无法获得真实的画面并将函数分配与所有其他对象区分开。
我编写了代码来说明这些方法,您可以找到它here。我使用EnumerateObjectAddresses
获取堆上对象的地址,并使用ETW的TraceEvenSession.Source.Clr.GCAllocationTick
获取有关段分配的事件。
首先,您可以通过调用System.GC.TryStartNoGCRegion
暂停GC,并用System.GC.TryStartNoGCRegion
取消暂停它。
仅知道分配了多少个bytes,有System.GC.EndNoGCRegion
返回为当前线程分配的总字节数。在要测量的代码之前和之后调用它,区别是分配大小。
计算分配数]有点棘手。可能有很多方法可以实现这些功能,而今天它们在某种程度上都是次优的。我可以想到一个主意:
从.NET Core 2.1开始,可以使用自定义GC,即所谓的local GC。据说开发经验,文档和实用性不是最好的,但是根据问题的具体情况,它可能对您有所帮助。
每次分配对象时,运行时调用System.GC.EndNoGCRegion
。 System.GC.GetAllocatedBytesForCurrentThread
是使用默认GC实现System.GC.GetAllocatedBytesForCurrentThread
(在37292行中实现的GCHeap :: Alloc)定义的Object* IGCHeap::Alloc(gc_alloc_context * acontext, size_t size, uint32_t flags)
。
与该人交谈的人将是IGCHeap
,其中有关于该主题的两个演示文稿:here,here。
我们可以直接使用默认的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在这里可能有点过大,但是我认为我应该把它扔掉。
而且,我还没有测试。您应该将其视为一个概念。