2012-09-27 54 views
2

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

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

方法:

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 = 200毫秒
方法执行2 = 12ms的
方法执行3 = 0毫秒

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

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

我的问题是:

  1. 为什么性能会随着时间的推移而改变,.NET在后台做些什么来促进这一点?

  2. 如何/为什么串行操作比并行操作更快?我已经确定我使操作复杂以正确地看到差异......在大多数情况下,串行操作似乎更快!

注:对于并行处理我正在测试的8芯的机器上。

+4

f你投票下来,你能解释为什么吗? – series0ne

+1

测量时间的错误方法,以及使用没有实际益处的微观基准。像这样的东西。有一点技巧可以制定一个实际可以证明任何事情的基准。 – TomTom

+0

忽略第一次运行,代码得到了JITted。我怀疑第二轮的缓慢与此有关,但我不知道。 –

回答

2

一些探索性能分析后,我发现,使用秒表不是衡量一个特定的任务对这个您的意见性能

(感谢前嫌,罗兰准确的方式!)

原因秒表是不准确的:

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

这就是说,使用秒表是可以随意探索的表现。考虑到这一点,我改进了我的分析算法。

在它只是执行传递给它的表达式之前,它现在可以重复遍历表达式几次,创建平均执行时间。第一次运行可以省略,因为这是JIT开始的地方,并且可能发生一些主要开销。可以理解的是,这绝不会像使用像Redgate's ANTS profiler这样的专业概要分析工具那么复杂,但对于更简单的任务来说没关系!

+0

蚂蚁当然是一个非常专业的软件。不幸的是,和基本上所有的分析器一样,问题很容易被隐藏起来,所以你无法找到它们。其主要的摘要方法似乎是与“热门路径”相同的“热门堆栈”。 [*本帖*](http://stackoverflow.com/a/25870103/23771)显示问题隐藏起来容易。 –

1

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

这是我的示例代码。 (I也试图与既没有差异静态和实例方法)

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 
+0

我认为在某种程度上,这取决于你正在分析的方法的时间/空间复杂性。在这里,你展示了一个O(1)的时间复杂度(执行该方法所花费的时间是不变的,因为该方法总是在做同样的工作)。考虑使用更复杂的方法运行您的分析器。 (我已经添加了一个到你的答案的底部) – series0ne

+0

O(n)顺序的好点(我猜这就是你的意思,对吧?)。 因此,在你原来的问题中,你基本上是在测量开销。不过,我仍然想知道为什么第三次执行比第二次执行快。在第三次执行中不存在的第二次执行中可能会有什么开销? 在使用'Guid'的例子中,开销似乎只出现在方法的第一次执行中。 – jbarrameda

+0

第一次执行可能归结为JIT和其他.NET在后台执行的操作。第二次和第三次执行时间的差别非常小,我只是把它放在CPU“噪音”上。为了获得真正准确的结果并理解你的代码的工作方式,这个解决方案并没有真正为大名字分析器提供很多好处,但我仍然认为它仅用于查看运行平均需要多长时间才有用 – series0ne