获取C#中的分配总数 [英] Get total number of allocations in C#

查看:110
本文介绍了获取C#中的分配总数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Is there a way to get the total number of allocations (note - number of allocations, not bytes allocated)? It can be either for the current thread, or globally, whichever is easier.

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

I want to check how many objects a particular function allocates, and while I know about the Debug -> Performance Profiler (Alt+F2), I would like to be able to do it programmatically from inside my program.

// 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.
}

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

Also, do I need to pause garbage collection to get accurate data, and can I do that?

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

Do I need to use the CLR Profiling API to achieve that?

推荐答案

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

You can record every allocation. But your logic to do this inside your process is flawed. .NET Core supports in process ETW data collection which makes it also possible to record all allocation events. See

  • 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/

从.NET Core 2.2开始,现在可以使用以下方式消耗CoreCLR事件: System.Diagnostics.Tracing.EventListener类.这些事件 描述GC,JIT,ThreadPool等运行时服务的行为, 和互操作.这些是作为 CoreCLR ETW提供程序.这允许应用程序使用这些 事件或使用传输机制将其发送到遥测 聚合服务.您可以在中查看如何订阅活动 以下代码示例:

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 evets(0x1)而不是将诊断过程中需要诊断的所有GC暂停时间和GC事件设为-1时,这应该会给出.

That should be giving when you enable GC evets (0x1) instead of -1 all the GC pause times and GC events you would need to diagnose yourself in-process.

.NET Core和.NET Framework早已内置了分配采样机制,该机制可在每5个分配事件/s GC_Alloc_Low或100个分配事件/s GC_Alloc_High分配的对象上采样对象分配指标.似乎没有办法获取所有分配事件,但是如果您阅读.NET Core代码

There are allocation sampling mechanism built into .NET Core and .NET Framework since ages which enable sampling object allocation metrics on every up to 5 alloc events/s GC_Alloc_Low or 100 alloc events/s GC_Alloc_High allocated object. There seems no way to get all allocation events but if you read the .NET Core code

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);
}

您发现

  1. 在启动过程时必须启用ETW分配分析(稍后启用将不起作用)
  2. 已启用GC_Alloc_High和GC_Allow_Low关键字

如果存在记录分配配置文件数据的ETW会话,则可以在.NET Core 2.1+进程中记录所有分配.

You can record all allocations inside a .NET Core 2.1+ process if an ETW session which record allocation profiling data is present.

示例:

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个数组的方法.

Now you can find all allocation events in the recorded ETL file. A method allocating 10 and another one with 5000 array allocations.

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

The reason why I did tell you that you logic is flawed is that even a simple operation like printing the allocation events to console will allocate objects. You see where this will end up? If you want to achieve that the complete code path must be allocation free which is not possible I guess because at least the ETW event listener needs to allocate your event data. You have reached the goal but crashed your application. I would therefore rely on ETW and record the data from the outside or with a profiler which needs for the same reason to be unmanaged.

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

With ETW you get all allocation stacks and type information which is all you need not only to report but also to find the offending code snippet. There is more to it about method inlining but that is already enough for an SO post I guess.

这篇关于获取C#中的分配总数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆