执行时间的变化

时间:2009-12-16 16:04:40

标签: c# .net

我一直在使用秒表类来分析一个方法,该方法精确到亚毫秒级。该方法在多个线程上运行数千次。

我发现大多数电话(90%以上)需要0.1毫秒,这是可以接受的。然而,偶尔我发现该方法需要花费几个数量级的时间,因此调用的平均时间实际上更像是3-4ms。

导致这种情况的原因是什么?

方法本身是从委托运行的,本质上是一个事件处理程序。

没有太多可能的执行路径,而且我还没有发现一条明显复杂的路径。

我怀疑垃圾收集,但我不知道如何检测它是否已经发生。

最后,我还在考虑日志记录方法本身是否会导致问题。 (记录器基本上是对写入控制台的静态类+事件监听器的调用。)

7 个答案:

答案 0 :(得分:2)

仅仅因为秒表具有高精度并不意味着其他事情不会妨碍 - 就像操作系统中断该线程做其他事情一样。垃圾收集是另一种可能性。写入控制台很容易造成类似的延迟。

实际上对个人通话时间感兴趣,还是整体表现很重要?运行方法数千次并查看总时间通常更有用 - 这比单个呼叫更能说明整体性能,这些呼叫可能会受到计算机上任意数量的影响。

答案 1 :(得分:1)

这可能取决于许多事情,你真的必须弄清楚你要推出哪一个。

  1. 我不太熟悉触发垃圾收集的内容以及它运行的线程,但这听起来像是一种可能性。

  2. 我首先想到的是分页。如果这是第一次运行该方法并且应用程序需要在某些代码中进行分页以运行该方法,那么它将等待该方法。或者,它可能是您在触发缓存未命中的方法中使用的数据,现在您必须等待。

  3. 也许您正在进行分配,分配器会进行一些额外的重组,以便为​​您提供所需的分配。

  4. 不确定如何使用秒表计算线程时间,但上下文切换可能就是您所看到的。

  5. 或者......它可能是完全不同的东西......

  6. 基本上,它可能是几件事之一,你真的必须看看代码本身,看看是什么导致你偶尔放慢速度。

答案 2 :(得分:1)

正如我评论的那样,如果你不愿意发布一些代码(这是最好的),你至少应该描述你的方法所做的事情。

也就是说,您可以通过一种方式判断垃圾收集是否已经发生(来自Windows):

  1. 运行perfmon(Start-> Run-> perfmon)
  2. 右键点击图表;选择“添加计数器......”
  3. 在“性能对象”下,选择“.NET CLR内存”
  4. 从那里您可以选择#Gen 0,1和2集合,然后单击“添加”
  5. 现在,您将在图表上看到所有.NET CLR垃圾收集的图表
  6. 在运行应用程序时保持此图表处于打开状态

  7. 编辑:如果您想知道在特定执行期间是否发生了收藏,为什么不这样做呢?

    int initialGen0Collections = GC.CollectionCount(0);
    int initialGen1Collections = GC.CollectionCount(1);
    int initialGen2Collections = GC.CollectionCount(2);
    
    // run your method
    
    if (GC.CollectionCount(0) > initialGen0Collections)
        // gen 0 collection occurred
    
    if (GC.CollectionCount(1) > initialGen1Collections)
        // gen 1 collection occurred
    
    if (GC.CollectionCount(2) > initialGen2Collections)
        // gen 2 collection occurred
    

    第二次编辑:关于如何减少方法中垃圾收集的几点:

    1. 您在评论中提到您的方法会将传入的对象添加到“大集合”中。根据您用于所述大集合的类型,可以减少垃圾收集。例如,如果您使用List<T>,则有两种可能性:

      一个。如果您事先知道要处理的对象数量,则应在构造时设置列表的容量:

      List<T> bigCollection = new List<T>(numObjects);

      湾如果知道您要处理的对象数量,请考虑使用LinkedList<T>而不是List<T>之类的内容。这样做的原因是,List<T>每当新项目超出其当前容量时自动调整大小;这会导致剩余的数组(最终)需要进行垃圾回收。 LinkedList<T>内部不使用数组(它使用LinkedListNode<T>个对象),因此不会导致此垃圾收集。

    2. 如果要在方法中创建对象(即,在方法的某处,您有一行或多行,如Thing myThing = new Thing();),请考虑使用资源池来消除需要不断构造对象,从而分配更多的堆内存。如果您需要了解有关资源池的更多信息,请查看the Wikipedia article on Object PoolsMSDN documentation on the ConcurrentBag<T> class,其中包含ObjectPool<T>的示例实现。

答案 3 :(得分:1)

很可能是GC。如果您使用的是分析器应用程序,例如Redgate的ANTS分析器,您可以根据应用程序的性能分析GC中的%时间,以查看正在进行的操作。

此外,您可以使用CLRProfiler ...

http://www.microsoft.com/downloads/details.aspx?familyid=A362781C-3870-43BE-8926-862B40AA0CD0&displaylang=en

最后,Windows性能监视器将显示给定运行应用程序的GC时间百分比。

这些工具将帮助您全面了解应用中的操作以及操作系统。

我确定你已经知道这些东西,但是像这样的微基准测试有时候可以用来确定一行代码与另一行代码的比较速度,但你通常也希望在典型的负载下分析你的应用程序。

知道给定的代码行比另一行快10倍是有用的,但是如果这行代码更容易阅读而不是紧密循环的一部分那么10x性能命中可能不是问题。

答案 4 :(得分:0)

您需要的是一个性能配置文件,可以准确地告诉您导致速度减慢的原因。这是一个快速list当然这里是ANTS个人资料。

在不知道您的操作正在做什么的情况下,听起来它可能是垃圾收集。然而,这可能不是唯一的原因。如果您正在读取或写入光盘,则您的应用程序可能必须等待其他人正在使用该磁盘。

如果您有一个多线程应用程序而另一个线程可能占用的处理器时间只占10%的运行时间,则可能会出现时序问题。这就是探查器可以提供帮助的原因。

答案 5 :(得分:0)

如果你只是在一个非常快速的功能上运行代码“数千次”,偶尔更长的时间可能很容易是由于系统上的瞬态事件(也许Windows决定是时候缓存一些东西)。

话虽如此,我建议如下:

  1. 多次运行该功能,并取平均值。
  2. 在使用该函数的代码中,确定所讨论的函数是否确实是一个瓶颈。为此使用分析器。

答案 6 :(得分:0)

它可能取决于您的操作系统,环境,页面读取,每秒CPU滴答等。

最现实的方法是运行数千次执行路径并取平均值。

但是,如果仅偶尔调用该日志记录类并将其记录到磁盘,那么如果它必须首先在驱动器上进行搜索,则很可能是一个减速因素。

阅读http://en.wikipedia.org/wiki/Profiling_%28computer_programming%29可以让您深入了解确定应用程序减速的更多技巧,同时您可能会发现有用的性能分析工具列表位于:

http://en.wikipedia.org/wiki/Visual_Studio_Team_System_Profiler

具体来说http://en.wikipedia.org/wiki/Visual_Studio_Team_System_Profiler如果你正在做c#的东西。

希望有所帮助!