长时间运行后CPU使用率高

时间:2016-12-03 15:03:50

标签: c# .net multithreading memory-leaks cpu-usage

我的应用程序存在问题,希望有人可以给我建议如何修复它。

我有多线程应用程序。它可以调整10-20个线程,在每个线程中我执行一些复杂的任务。

Thread thread = new Thread(ProcessThread);
thread.Start();

private void ProcessThread()
{
    while(IsRunning)
    {
        // do some very complex operations: grab HTTP pages. Save to files. Read from files. Run another threads etc.
    }
}

一开始app使用大约10%的CPU和140Mb的内存。但在1000次执行后,CPU使用率为25%-30%,内存为1200Mb。我知道可能我的代码中有内存泄漏,我会尝试修复它。但是CPU发生了什么?它为何成长?每次执行都会执行与开始和稍后相同的操作(例如,打开网页,获取一些信息并将其保存到文件中)。

我认为这个问题可能与GC有关。更多的内存应用需要更多的CPU需要清理内存吗?

另一个问题,请问一个好的工具如何衡量我的应用程序中CPU的含量?

也许你可以推荐一个好的工具来分析内存并检查泄漏的位置?我试过JetBrains dotMemory,但并不太了解。也许你可以帮助我。 这是统计: http://prntscr.com/dev067 http://prntscr.com/dev7a2 正如我所见,我没有太多非托管内存。但与此同时,我发现字符串存在问题,但无法理解GC必须清除它的错误吗?

感谢我可以改进的任何建议和建议。

5 个答案:

答案 0 :(得分:0)

除非你包装本机类型,否则我怀疑你有内存泄漏。内存使用率很可能是由于GC的工作方式。

GC不会收集循环中的所有死物品。它将使用。这意味着只有在需要空间时才会收集旧对象。因此,内存使用量将增加,直到第一次收集。然后,只收集了一小部分项目,这会稍微降低内存使用量,但不会完全降低。 GC也不一定会将释放的内存返回给操作系统。

在Visual Studio的高版本中,您将找到一个内存分析器。

答案 1 :(得分:0)

目前尚不清楚什么是“一些复杂的任务”。 这特别让我担心:

// do some very complex operations: grab HTTP pages. Save to files. Read from files. Run another threads etc.

好吧,如果你确实从你的进程线程开始新线程,那么提高CPU使用率是完全有意义的。从性能角度来看,创建新线程的成本很高,这就是我们使用线程池(回收线程)的原因。

内存使用也是如此。更多线程=每个线程需要更多内存(每个线程都有自己的堆栈,需要额外的内存)...

另外,我不相信GC是罪魁祸首。

在我能帮助你找到这种行为的原因之前,有很多问题需要回答,在我们责备GC之前:): 1)你是否在你的程序开始时开始所有20个线程? 2)你是否从已经运行的线程创建新线程? 3)这些线程的终止条件是什么?他们真的会终止吗?

我建议使用dotTrace来确定CPU使用率。 不幸的是,我没有使用任何工具来分析内存使用情况,所以我不推荐任何工具。

答案 2 :(得分:0)

我查看了你的屏幕截图,从中我看到你有许多对象存活在第0代集合中,因此它们被升级到第1代然后升级到第2代。这可能是内存泄漏的标志,但不一定。没有看到你的代码,很难说。我所能说的就是你在很长一段时间内保留物品。这可能是需要的,但我再也看不到代码。

关于GC的一点

当GC唤醒清理时,它会分析托管堆以查看哪些对象没有生根,并将它们全部标记为可以收集。这称为标记阶段。然后它开始释放内存。这称为扫描阶段。如果它无法清理任何东西,那些对象会转到第1代。稍后GC会再次唤醒并重复上述内容,但这一次,因为第1代中有项目,如果无法收集它们,它们将转到第2代。可能是个坏兆头。这些物体真的需要长时间存在吗?

那你能做什么?

你说GC必须清理东西。是的,GC会清理东西,但前提是你没有引用它们。如果某个对象已植根,那么GC将无法清除它。

记住使用GC编写代码

要开始调查,您需要调查代码并确保代码是用GC编写的。浏览您的代码并列出您正在使用的所有对象。如果任何对象属于实现IDisposable的类,则将它们包装在using语句中:

using (Font font1 = new Font("Arial", 10.0f)) 
{
    // work here depends on font1
} 

如果您需要在其中一个类中使用font1作为类级变量,那么您必须在其上调用Dispose时做出决定:至少这个类应该实现IDisposable和呼叫font1上的处理。此类的用户(在此类上调用new的任何代码)应该将此类与using语句一起使用,或者调用`Dispose'在它上面。

不要让物品长于你需要的时间。

还需要更多调查吗?

一旦您调查了代码并确保您的代码对GC更友好并且您仍然遇到问题,那么请使用工具进行调查。 Here是一篇很好的文章,可以帮助您完成这一部分。

有些人误以为调用GC.Collect()会解决内存泄漏问题。这根本不是真的。强制垃圾收集仍然遵循相同的规则,如果对象是root用户,则可以无限期地调用GC.Collect(),仍然不会清除对象。

这是一个示例应用程序,它将显示:

public class Writer : IDisposable
{
    public void Dispose()
    {

    }

    public void Write(string s)
    {
        Console.WriteLine(s);
    }
} 

class Program
{

    static void Main(string[] args)
    {
        Writer writer = new Writer();
        writer.Write("1");
        writer.Dispose();

        // writer will still be around because I am referencing it (rooted)
        writer.Write("2");

        GC.Collect();

        // calling GC.Collect() has no impact since writer is still rooted
        writer.Write("3");
        Console.ReadKey();

    }
}

答案 3 :(得分:0)

我只能评论您遇到CPU问题而不是GC问题。

  1. 我建议您验证您是否只创建了预期的数字线程。如果您碰巧有线程启动线程,那么它很容易滑落。
  2. 如另一条评论所述,线程创建和销毁成本很高。如果您一直在创建和销毁线程,这将对您的性能产​​生显着影响。
  3. 我怀疑你的减速原因是内存颠簸,这意味着每个线程使用的内存量足够大,和/或所有线程使用的总量足够大,导致内存页面被换入和换出每时每刻。在某些情况下,处理器可以花费更多时间将内存与磁盘交换,而不是花在执行线程上。
  4. 以下是我的建议:

    1. 使用线程池。这样可以最大限度地缩短创建和销毁线程所需的时间。这也将限制您正在使用的线程数。
    2. 确保每个线程在换出之前执行一段合理的时间 - 也就是说,您没有线程上下文颠簸。
    3. 确保每个线程使用的内存量并不比预期的大,以防止内存抖动。
    4. 如果必须为每个线程使用大量内存,请确保您的使用模式不会导致大量内存分页。

答案 4 :(得分:0)

使用"Start collecting allocations data immediately" enabled运行您的应用程序,稍后获取快照并查看at memory traffic view。 要了解占用内存的内容"All objects" grouped by type并查看哪些类型占用的内存最多。

对于性能分析,我可以推荐JetBrains dotTrace(时间轴模式,适用于您的情况)。