我试图找出为什么我的应用程序会随着时间的推移而变慢。应用程序将要做的工作捆绑成可以同时执行的批处理。每一批都必须等待前一批完成。批处理在更新函数中每 30 毫秒执行一次。在代码中,它看起来像这样:
List<List<Action>> batches = new List<List<Action>>(); // class member
foreach (var batch in batches) // inside of the update function
{
Parallel.ForEach(batch, action => action());
}
几天后,我发现执行所有批次的时间越来越长。起初,执行大约需要 15ms,然后几天后需要超过 100ms。我的目标是找出为什么执行时间不断增加。
在调试时,我发现批次的数量和每个批次中的动作量保持不变。
使用 dotTrace 进行分析表明,有时批处理中的一个操作需要更长的时间,并且Parallel.ForEach
会(并且应该)等到所有先前的操作完成。在启动应用程序后立即进行分析时,延迟仅每几百次更新可见。几天后,几乎每次更新都会出现延迟。
这就是它在分析器中的样子;我用不同的绿色标记了各个更新周期并对其进行了编号。如您所见,第二次更新有明显的延迟(黄色)。
在黄色期间,没有其他线程在工作(至少根据分析器)。当一一选择线程时,除一个之外的所有线程都不在执行操作。正如您在下一张图片中看到的那样,仍在执行操作的一个线程没有显示任何 CPU 负载,并且所有时间都花在ntdll.dll
.
在这种情况下,调用GetEnumerator
大约需要 24 毫秒,但我也看到过类似ToString()
或类似的函数冻结ToArray()
。
我观察到的相关内容:
- 冻结与特定功能无关,可能发生在应用程序的任何部分
- 花费的时间总是显示在
ntdll.dll
- 通常发生在 GC 之后
- 在分析时间运行的所有 GC 都是 gen0
- 被调用函数分配内存