gpt4 book ai didi

c# - 获取 C# 中的分配总数

转载 作者:行者123 更新时间:2023-12-03 16:21:47 26 4
gpt4 key购买 nike

有没有办法获得分配的总数(注意 - 分配的数量,而不是分配的字节数)?它可以是当前线程,也可以是全局的,以更容易者为准。

我想检查一个特定函数分配了多少对象,虽然我知道 Debug -> 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 Profiling API 来实现吗?

最佳答案

您可以记录每个分配。但是您在流程中执行此操作的逻辑是有缺陷的。 .NET Core 支持进程中的 ETW 数据收集,这使得记录所有分配事件成为可能。

  • https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-2-2
  • https://devblogs.microsoft.com/dotnet/a-portable-way-to-get-gc-events-in-process-and-no-admin-privilege-with-10-lines-of-code-and-ability-to-dynamically-enable-disable-events/

  • Starting with .NET Core 2.2, CoreCLR events can now be consumed using the System.Diagnostics.Tracing.EventListener class. These events describe the behavior of such runtime services as GC, JIT, ThreadPool, and interop. These are the same events that are exposed as part of the CoreCLR ETW provider. This allows for applications to consume these events or use a transport mechanism to send them to a telemetry aggregation service. You can see how to subscribe to events in the following code sample:

    internal sealed class SimpleEventListener : EventListener
    {
    // Called whenever an EventSource is created.
    protected override void OnEventSourceCreated(EventSource eventSource)
    {
    // Watch for the .NET runtime EventSource and enable all of its events.
    if (eventSource.Name.Equals("Microsoft-Windows-DotNETRuntime"))
    {
    EnableEvents(eventSource, EventLevel.Verbose, (EventKeywords)(-1));
    }
    }

    // Called whenever an event is written.
    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
    // Write the contents of the event to the console.
    Console.WriteLine($"ThreadID = {eventData.OSThreadId} ID = {eventData.EventId} Name = {eventData.EventName}");
    for (int i = 0; i < eventData.Payload.Count; i++)
    {
    string payloadString = eventData.Payload[i]?.ToString() ?? string.Empty;
    Console.WriteLine($"\tName = \"{eventData.PayloadNames[i]}\" Value = \"{payloadString}\"");
    }
    Console.WriteLine("\n");
    }
    }


    当您启用 GC 事件 (0x1) 而不是 -1 时,这应该会提供您在进程中诊断自己所需的所有 GC 暂停时间和 GC 事件。

    自古以来,.NET Core 和 .NET Framework 中就内置了分配采样机制,可以在每 5 个分配事件/秒 GC_Alloc_Low 或 100 个分配事件/秒 GC_Alloc_High 分配的对象上采样对象分配指标。似乎无法获取所有分配事件,但如果您阅读 .NET Core 代码
    BOOL ETW::TypeSystemLog::IsHeapAllocEventEnabled()
    {
    LIMITED_METHOD_CONTRACT;

    return
    // Only fire the event if it was enabled at startup (and thus the slow-JIT new
    // helper is used in all cases)
    s_fHeapAllocEventEnabledOnStartup &&

    // AND a keyword is still enabled. (Thus people can turn off the event
    // whenever they want; but they cannot turn it on unless it was also on at startup.)
    (s_fHeapAllocHighEventEnabledNow || s_fHeapAllocLowEventEnabledNow);
    }

    你发现你可以通过 ETW 获取所有的分配事件
  • ETW 分配分析必须在进程启动时启用(稍后启用将不起作用)
  • GC_Alloc_High AND GC_Allow_Low 关键字已启用

  • 如果存在记录分配分析数据的 ETW session ,则可以在 .NET Core 2.1+ 进程中记录所有分配。

    样本:
    C>perfview collect  c:\temp\perfViewOnly.etl -Merge:true -Wpr -OnlyProviders:"Microsoft-Windows-DotNETRuntime":0x03280095::@StacksEnabled=true
    C>AllocTracker.exe
    Microsoft-Windows-DotNETRuntime
    System.Threading.Tasks.TplEventSource
    System.Runtime
    Hello World!
    Did allocate 24 bytes
    Did allocate 24 bytes
    Did allocate 24 bytes
    Did allocate 76 bytes
    Did allocate 76 bytes
    Did allocate 32 bytes
    Did allocate 64 bytes
    Did allocate 24 bytes
    ... endless loop!

    using System;
    using System.Diagnostics.Tracing;

    namespace AllocTracker
    {
    enum ClrRuntimeEventKeywords
    {
    GC = 0x1,
    GCHandle = 0x2,
    Fusion = 0x4,
    Loader = 0x8,
    Jit = 0x10,
    Contention = 0x4000,
    Exceptions = 0x8000,
    Clr_Type = 0x80000,
    GC_AllocHigh = 0x200000,
    GC_HeapAndTypeNames = 0x1000000,
    GC_AllocLow = 0x2000000,
    }

    class SimpleEventListener : EventListener
    {
    public ulong countTotalEvents = 0;
    public static int keyword;

    EventSource eventSourceDotNet;

    public SimpleEventListener() { }

    // Called whenever an EventSource is created.
    protected override void OnEventSourceCreated(EventSource eventSource)
    {
    Console.WriteLine(eventSource.Name);
    if (eventSource.Name.Equals("Microsoft-Windows-DotNETRuntime"))
    {
    EnableEvents(eventSource, EventLevel.Informational, (EventKeywords) (ClrRuntimeEventKeywords.GC_AllocHigh | ClrRuntimeEventKeywords.GC_AllocLow) );
    eventSourceDotNet = eventSource;
    }
    }
    // Called whenever an event is written.
    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
    if( eventData.EventName == "GCSampledObjectAllocationHigh")
    {
    Console.WriteLine($"Did allocate {eventData.Payload[3]} bytes");
    }
    //eventData.EventName
    //"BulkType"
    //eventData.PayloadNames
    //Count = 2
    // [0]: "Count"
    // [1]: "ClrInstanceID"
    //eventData.Payload
    //Count = 2
    // [0]: 1
    // [1]: 11

    //eventData.PayloadNames
    //Count = 5
    // [0]: "Address"
    // [1]: "TypeID"
    // [2]: "ObjectCountForTypeSample"
    // [3]: "TotalSizeForTypeSample"
    // [4]: "ClrInstanceID"
    //eventData.EventName
    //"GCSampledObjectAllocationHigh"
    }
    }

    class Program
    {
    static void Main(string[] args)
    {
    SimpleEventListener.keyword = (int)ClrRuntimeEventKeywords.GC;
    var listener = new SimpleEventListener();

    Console.WriteLine("Hello World!");

    Allocate10();
    Allocate5K();
    GC.Collect();
    Console.ReadLine();
    }
    static void Allocate10()
    {
    for (int i = 0; i < 10; i++)
    {
    int[] x = new int[100];
    }
    }

    static void Allocate5K()
    {
    for (int i = 0; i < 5000; i++)
    {
    int[] x = new int[100];
    }
    }
    }

    }

    现在您可以在记录的 ETL 文件中找到所有分配事件。一种分配 10 个数组的方法,另一种分配 5000 个数组的方法。

    PerfView Allocation Recording

    我之所以告诉你你的逻辑是有缺陷的,是因为即使是像打印分配事件到控制台这样的简单操作也会分配对象。你知道这会在哪里结束吗?
    如果您想实现完整的代码路径必须是免费的,我猜这是不可能的,因为至少 ETW 事件监听器需要分配您的事件数据。
    你已经达到了目标,但你的应用程序崩溃了。因此,我将依赖 ETW 并记录来自外部的数据或使用出于同样原因需要不受管理的分析器。

    使用 ETW,您可以获得所有分配堆栈和类型信息,您不仅需要报告这些信息,而且还需要找到有问题的代码片段。关于方法内联还有更多内容,但我猜这对于 SO 帖子来说已经足够了。

    关于c# - 获取 C# 中的分配总数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61081063/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com