3

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

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

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

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

4

6 回答 6

6

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

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

如果这不可能,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 ); }
}
于 2009-09-28T20:49:50.390 回答
3

您可以使用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 (with PostSharp)。否则,您很可能最终会花一天左右的时间来掌握 PostSharp 的窍门,但这仍然是值得的。

请注意,我知道您说过您负担不起将框架集成到代码库中的费用,但这并不像让 PostSharp 对您的代码运行一些编译后转换那样真正“集成”。

于 2009-09-28T20:19:17.363 回答
1

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

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

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

[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 获取此软件包。

Nuget 命令: PM> Install-Package pUnit

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

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

我在下面的博客文章中更详细地介绍了实现。

于 2012-02-18T13:29:36.923 回答
0

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

于 2009-09-28T20:20:21.760 回答
0

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

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

于 2009-09-28T20:20:52.280 回答
0

我在 C# 中进行性能调优。我需要的只是这种技术。没什么大不了的。

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

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

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

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

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

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

于 2009-09-28T23:11:08.413 回答