.NET 中的性能分析

Posted

技术标签:

【中文标题】.NET 中的性能分析【英文标题】:Performance profiling in .NET 【发布时间】:2012-09-19 16:34:31 【问题描述】:

我写了一个类,它使用Stopwatch 来分析方法和for/foreach 循环。使用forforeach 循环,它会针对Parallel.ForParallel.ForEach 实现测试标准循环。

你会像这样编写性能测试:

方法:

PerformanceResult result = Profiler.Execute(() =>  FooBar(); );

For循环:

SerialParallelPerformanceResult result = Profiler.For(0, 100, x =>  FooBar(x); );

ForEach 循环:

SerialParallelPerformanceResult result = Profiler.ForEach(list, item =>  FooBar(item); );

每当我运行测试(.Execute.For.ForEach 之一)时,我都会将它们放在一个循环中,以便查看性能如何随时间变化。

性能示例可能是:

方法执行 1 = 200ms 方法执行 2 = 12ms 方法执行 3 = 0ms

对于执行 1 = 300ms(串行),100ms(并行) 执行 2 = 20ms(串行),75ms(并行) 对于执行 3 = 2ms(串行),50ms(并行)

ForEach 执行 1 = 350ms(串行),300ms(并行) ForEach 执行 2 = 24ms(串行),89ms(并行) ForEach 执行 3 = 1ms(串行),21ms(并行)

我的问题是:

    为什么性能会随着时间而变化,.NET 在后台做了什么来促进这一点?

    串行操作如何/为什么比并行操作更快?我已确保将操作复杂化以正确查看差异...在大多数情况下,串行操作似乎更快!?

注意:对于并行处理,我在 8 核机器上进行测试。

【问题讨论】:

如果你投反对票,你能解释一下原因吗! 错误的计时方法,并使用了对现实世界没有好处的微基准。像这样的东西。制定一个可以证明任何事情的基准是一种小技巧。 忽略第一次运行,代码正在 JITted。我怀疑第二次运行的缓慢与此有关,但我不知道。 除了 Loren 所说的之外,执行工作的时间可能非常短,以至于您所测量的只是围绕该工作的开销。另外,请注意,秒表测量的是时钟时间,而不是 CPU 时间,因此它会受到计时测试期间运行的其他事物的影响。 @hatchet,我使用秒表的原因与我之前提出的问题有关,并得到了这个答案:***.com/a/12618494/1033686。但是如果你说我需要测量 CPU 时间,那么 .NET 有什么可以促进这个吗? 【参考方案1】:

在对性能分析进行了更多探索之后,我发现使用秒表并不是衡量特定任务性能的准确方法

(感谢斧头和 Loren 提供的 cmets!)

秒表不准确的原因:

    测量以经过的时间(以毫秒为单位)计算,而不是 CPU 时间。 测量可能会受到背景“噪音”和线程密集型进程的影响。 测量不考虑 JIT 编译和开销。

话虽如此,使用秒表可以随意探索性能。考虑到这一点,我在一定程度上改进了我的分析算法。

在它简单地执行传递给它的表达式之前,它现在可以对表达式进行多次迭代,从而建立平均执行时间。第一次运行可以省略,因为这是 JIT 启动的地方,并且可能会出现一些主要开销。可以理解,这永远不会像使用像 Redgate's ANTS profiler 这样的专业分析工具那样复杂,但对于更简单的任务来说没问题!

【讨论】:

ANTS 无疑是一款非常专业的软件。不幸的是,就像基本上所有的分析器一样,问题很容易被它隐藏,所以你无法找到它们。它的主要概括方法似乎是与“热路径”相同的“热堆栈”。 This post 展示了隐藏问题是多么容易。【参考方案2】:

根据我上面的评论:我自己做了一些简单的测试,随着时间的推移没有发现任何差异。你能分享你的代码吗?我会把我的答案写在这里,因为它不适合这里。

这是我的示例代码。 (我也尝试了静态和实例方法,没有区别)

class Program

    static void Main(string[] args)
    
        int to = 50000000;
        OtherStuff os = new OtherStuff();

        Console.WriteLine(Profile(() => os.CountTo(to)));
        Console.WriteLine(Profile(() => os.CountTo(to)));
        Console.WriteLine(Profile(() => os.CountTo(to)));
    

    static long Profile(Action method)
    
        Stopwatch st = Stopwatch.StartNew();
        method();
        st.Stop();
        return st.ElapsedMilliseconds;
    


class OtherStuff

    public void CountTo(int to)
    
        for (int i = 0; i < to; i++)
        
            // some work...
            i++;
            i--;
        
    

示例输出为:

331
331
334

考虑改为执行此方法:

class OtherStuff
    
        public string CountTo(Guid id)
        
            using(SHA256 sha = SHA256.Create())
            
                int x = default(int);
                for (int index = 0; index < 16; index++)
                
                    x = id.ToByteArray()[index] >> 32 << 16;
                
                RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
                byte[] y = new byte[1024];
                rng.GetBytes(y);
                y = y.Concat(BitConverter.GetBytes(x)).ToArray();
                return BitConverter.ToString(sha.ComputeHash(BitConverter.GetBytes(x).Where(o => o >> 2 < 0).ToArray()));
            
        
    

示例输出:

11 
0 
0

【讨论】:

我认为在某种程度上,这取决于您正在分析的方法的时间/空间复杂性。在这里,您展示了 O(1) 时间复杂度(执行该方法所花费的时间是恒定的,因为该方法总是在做同样的工作)。考虑针对更复杂的方法运行分析器。 (我在你的答案底部添加了一个) O(n) 订单的好点(我想这就是你的意思,对吧?)。因此,在您最初的问题中,您似乎基本上是在测量开销。但是,我仍然想知道为什么第三次执行比第二次快。在第 2 次执行中可能存在哪些在第 3 次执行中不存在的开销?在使用Guid 的示例中,开销似乎只存在于方法的第一次执行中。 第一次执行很可能归结为 JIT 和 .NET 在后台执行的其他操作。第二次和第三次执行之间的时间差异是如此微小,以至于我将其归结为 CPU“噪音”。为了获得真正准确的结果,并了解您的代码是如何工作的,这个解决方案并没有比大牌分析器提供太多的好处,但我仍然认为看看平均运行需要多长时间是很有用的 我认为第 2 次运行的 20 毫秒到第 3 次运行的 0 毫秒对于 CPU“噪音”来说是很多的。我会测试最多 20 次运行来验证。无论如何,谢谢你的这篇文章。关于大型分析器,有时它们不可用,可能是因为 $ 原因,或者在我的情况下,我在应用程序中使用此信息(估计的运行成本),我可以粗略估计。跨度>

以上是关于.NET 中的性能分析的主要内容,如果未能解决你的问题,请参考以下文章

除了 FxCop 之外,.net 中的代码分析还有其他选择吗?

asp.net中的ADO.NET实体框架适用于不同的数据库及其性能

.NET Core中的性能测试工具BenchmarkDotnet

C# 中的 ACEDAO 性能比 VB.net 慢得多

.NET 构建管道中的静态代码分析

使用 Stopwatch 在 .NET 中的方法级别自动分析