【问题标题】:Method Profiling Using Attributes使用属性的方法分析
【发布时间】:2009-09-28 20:16:23
【问题描述】:

是否可以通过 .NET 中的属性来分析单个方法?

我目前正试图找出大量使用静态方法的大型遗留应用程序中的一些瓶颈。目前,集成框架根本不是一种选择。由于大多数调用使用静态方法,接口和依赖注入不可用。破解代码以记录诊断信息也不是一个可行的解决方案。

我知道市场上有一些分析工具,但它们目前超出了预算。理想情况下,我将能够创建自己的自定义属性,该属性将记录有关方法入口和方法出口的一些基本信息。我从来没有真正使用过自定义属性,因此如果能深入了解这是否可能,我将不胜感激。

如果可能,我想通过配置文件启用分析。这将支持通过单元和集成测试进行分析。

【问题讨论】:

    标签: .net profiling custom-attributes


    【解决方案1】:

    你不能为你正在做的事情使用属性。但是,您确实有一些选择:

    首先,许多分析器工具(如RedGate ANTS)相对便宜(200-300 美元),易于使用,并且大多数提供几周的免费评估期 - 所以你可以看看它们是否会在您决定是否购买它们之前,为您提供您现在需要的电梯。此外,.NET CLR profiler 可以免费下载。

    如果这不可能,PostSharp 可能是将此类逻辑编织到代码中的最简单方法。

    最后,如果您出于某种原因无法使用 PostSharp,并且您愿意为代码添加属性,那么您也可以以 using 块的形式为每个方法添加一个简单的检测块:

    public void SomeMethodToProfile()
    {
        // following line collects information about current executing method
        // and logs it when the metric tracker is disposed of
        using(MetricTracker.Track(MethodBase.GetCurrentMethod()))
        { 
            // original code here...
        }
    }
    

    典型的 MetricTracker 实现如下所示:

    public sealed class MetricTracker : IDisposable
    {
        private readonly string m_MethodName;
        private readonly Stopwatch m_Stopwatch;
    
        private MetricTracker( string methodName ) 
           { m_MethodName = methodName; m_Stopwatch = Stopwatch.StartNew(); }
    
        void IDisposable.Dispose()
           { m_Stopwatch.Stop(); LogToSomewhere(); }
    
        private void LogToSomewhere()
           { /* supply your own implementation here...*/ }
    
        public static MetricTracker Track( MethodBase mb )
           { return new MetricTracker( mb.Name ); }
    }
    

    【讨论】:

    • 我喜欢这个实现,但它不能用于我们目前的情况。尽管与其他所有内容相比它非常轻量级,但它非常具有侵入性,因为我需要修改大量现有代码。其他一些人提到了 PostSharp。从我最初的评论来看,它似乎可以满足我的需要。感谢您的意见。
    • 非常聪明...我使用 PostSharp 进行类似的跟踪——但这很好而且轻量级——赞。注意 - MetricTracker 中的最后一个方法必须是:“public static MetricTracker Track(MethodBase method)”——而不是“void”
    • 非常好的和简单的解决方案:) 正在研究使用属性来做到这一点,但会导致大量开销。所以我同意你的建议,谢谢:)
    【解决方案2】:

    您可以使用PostSharp 进行一些编织,基本上是转动:

    [Profiled]
    public void Foo()
    {
         DoSomeStuff();
    }
    

    进入

    public void Foo()
    {
        Stopwatch sw = Stopwatch.StartNew();
        try
        {
            DoSomeStuff();
        }
        finally
        {
            sw.Stop();
            ProfileData.AddSample("Foo", sw.Elapsed);
        }
    }
    

    确实,查看 PostSharp 文档,如果您负担得起的话,您应该可以使用 Gibraltar(使用 PostSharp)。否则,您很可能会花一天左右的时间来掌握 PostSharp 的窍门,但这仍然是值得的。

    请注意,我知道您说过您无法将框架集成到您的代码库中,但您并不会真正“集成”,而是让 PostSharp 对您的代码运行一些编译后转换。

    【讨论】:

    • 谢谢。 PostSharp 似乎提供了我所需要的东西,并且不需要对现有代码进行任何重大更改。它绝对满足我对本地开发盒的所有要求,但我不知道在生产中是否有用,因为它似乎需要安装在运行应用程序的服务器上。谢谢你的好建议!
    • @Michael:它一定需要安装在那里吗?我会希望对于非常简单的编织 - 特别是如果你编写自己的方面 - 在重写后你不应该需要任何 PostSharp 痕迹。不过我已经有一段时间没有尝试过了。
    • 我不能说它肯定需要安装。我只试了几个小时。入门教程中的步骤提到了安装。起初我试图只引用没有安装的二进制文件,但它不起作用。安装后相同的代码,就像一个魅力。我希望有一种方法可以在不安装在每台机器上的情况下使用它,但我还有一些学习要做。
    • @Michael:它需要安装在 build 机器上,但这并不一定意味着它需要安装在 deployment 机器上机器。
    【解决方案3】:

    我正在寻找与您描述的非常相似的东西。我找不到这样的框架,所以我推出了自己的框架。我应该注意,这非常简单,但有时简单就是好的!

    我将其描述为基准测试符合单元测试。这个概念是为了测量或比较速度而隔离代码部分。

    属性用法的典型示例如下所示:

    [ProfileClass]
    public class ForEachLoopBenchmarks
    {
        [ProfileMethod]
        public void ForLoopBenchmark()
        {
            List<int> list = GetNumberList();
    
            for (int i = 0; i < list.Count; i++)
            {
            }
        }
    
        [ProfileMethod]
        public void ForEachLoopBenchmark()
        {
            List<int> list = GetNumberList();
    
            foreach (int i in list)
            {
            }
        }
    
        private List<int> GetNumberList()
        {
            List<int> list = new List<int>();
            for (int i = 0; i < 1000; i++)
            {
                list.Add(i);
            }
            return list;
        }
    }
    

    然后您创建一个控制台应用程序并将以下代码粘贴到 Main 方法中,并添加对包含前面描述的用属性修饰的类的程序集的引用。然后将每个方法的执行时间(运行 1000 次)输出到控制台。

    class Program
    {
        static void Main(string[] args)
        {
            ProfileRunner rp = new ProfileRunner();
            rp.Run();
        }
    }
    

    控制台输出如下所示:

    您需要将pUnit.dll的引用添加到控制台应用程序和包含带有属性标记的方法的类库。

    您可以从 Nuget here 以包的形式获取它。

    Nuget 命令: PM> Install-Package pUnit

    如果您想要完整的源代码,您可以在 Github here 上找到它。

    我基于这个问题的实际测量执行时间的方法:https://stackoverflow.com/a/1048708/1139752

    我在下面的blog post 中更详细地介绍了实现。

    【讨论】:

      【解决方案4】:

      还有 some 免费的分析器工具可能值得一看。

      【讨论】:

        【解决方案5】:

        我在 C# 中进行性能调优。 All I need is this technique.没什么大不了的。

        它基于一个简单的想法。如果您等待的时间比必要的长得多,这意味着程序的一部分也等待的时间比必要的长得多,因为有些事情并不真正需要完成。

        它是如何等待的?几乎总是在调用站点上,在调用堆栈上。

        因此,如果您只是在等待时暂停它,并查看调用堆栈,您会看到它在等待什么,如果它不是真的需要(通常不是),您会看到为什么马上。

        不要只相信一个样本 - 多做几次。任何出现在多个堆栈样本上的东西,如果你能做点什么,就会节省很多时间。

        所以你看,这与计时函数或计算它们被调用的次数无关。这是关于在未事先通知的情况下加入该程序几次,并询问它在做什么以及为什么。如果某件事浪费了 80%(或 20% 或其他)的时间,那么 80% 的周期将处于不是真正必要的状态,所以只需顺道看看它们。您不需要精确测量。

        它适用于大问题。它也适用于小问题。如果你不止一次地做整个事情,随着程序变得越来越快,小问题会变得相对更大,更容易找到。

        【讨论】:

          【解决方案6】:

          您不能通过属性来实现这一点,除非您想通过PostSharp 之类的方式使用面向方面编程来实现这一点。

          但是,您可以根据定义(可能在构建配置中设置)将条件逻辑放入其中。这可能会根据您当前的编译设置打开或关闭您的日志记录。

          【讨论】:

            猜你喜欢
            • 2021-01-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-11-11
            • 1970-01-01
            • 1970-01-01
            • 2012-12-05
            • 2012-04-22
            相关资源
            最近更新 更多