【问题标题】:High CPU usage after long running长时间运行后CPU使用率高
【发布时间】:2016-12-03 15:03:50
【问题描述】:

我的应用程序有问题希望有人能给我建议如何解决它。

我有多线程应用程序。它调节 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.
    }
}

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

我认为问题可能出在 GC 上。应用程序占用更多内存,需要更多 CPU 清理内存?

另一个问题,您能否建议一个好的工具来测量我的应用程序中占用 CPU 的资源?

也许你可以推荐一个好的工具来分析内存并检查它在哪里泄漏?我尝试了 JetBrains dotMemory,但不太了解。也许你可以帮助我。 这是统计数据: http://prntscr.com/dev067 http://prntscr.com/dev7a2 正如我所见,我没有太多的非托管内存。但同时我看到了字符串的问题,但不明白什么是错误的,因为 GC 必须清理它?

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

【问题讨论】:

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


    【解决方案1】:

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

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

    在更高版本的 Visual Studio 中,您会发现内存分析器。

    【讨论】:

      【解决方案2】:

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

      // 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 使用率。 不幸的是,我没有使用任何工具来分析内存使用情况,所以我不能推荐任何工具。

      【讨论】:

        【解决方案3】:

        我查看了您的屏幕截图,从中我看到您有许多对象在第 0 代集合中幸存,因此它们正在升级到第 1 代,然后升级到第 2 代。这可能是内存泄漏的迹象,但不一定。没有看到你的代码很难说。我能说的就是你把你的物品保存了很长时间。这可能是需要的,但如果没有看到代码,我又无法分辨。

        一点关于 GC

        当 GC 醒来进行清理时,它会分析托管堆以查看哪些对象没有被 root 并标记它们已准备好收集。这称为标记阶段。然后它开始释放内存。这称为扫描阶段。如果它不能清理任何东西,这些对象就会进入第 1 代。稍后 GC 会再次唤醒并重复上述操作,但这一次,由于第 1 代中有项目,如果无法收集它们,它们将进入第 2 代。这可能是一个不好的迹象。这些物体真的需要存在那么长时间吗?

        那你能做什么?

        你说 GC 必须清理东西。好吧,是的,GC 会清理东西,但前提是你不引用它们。如果一个对象是 root 的,那么 GC 将无法清理它。

        在编写代码时考虑到 GC

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

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

        如果你需要在你的一个类中将font1 作为类级变量,那么你必须决定何时在其上调用Dispose:至少这个类应该实现 IDisposable 并在 @ 上调用 dispose 987654325@。这个类的用户(任何在这个类上调用 new 的代码)应该要么使用这个类和 using 语句,要么在它上面调用 `Dispose'。

        不要将物品存放的时间超过您需要的时间。

        还需要进一步调查吗?

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

        有些人误以为调用GC.Collect() 会解决内存泄漏问题。这根本不是真的。强制垃圾回收仍将遵循相同的规则,如果对象已被植根,您可以无限期地调用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();
        
            }
        }
        

        【讨论】:

          【解决方案4】:

          我只能评论为什么你有 CPU 问题而不是 GC。

          1. 我建议您确认您只创建了您期望的线程数。如果你碰巧有线程启动线程,很容易滑倒。
          2. 正如另一条评论中提到的,线程创建和销毁的成本很高。如果您一直在创建和销毁线程,这将对您的性能产​​生显着影响。
          3. 我怀疑您的速度变慢的原因是内存抖动,这意味着每个线程使用的内存量足够大,和/或所有线程使用的总内存量足够大以导致内存页面被换入和换出每时每刻。在某些情况下,处理器与磁盘交换内存和从磁盘交换内存所花费的时间可能比执行线程所花费的时间更多。

          以下是我的建议:

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

          【讨论】:

            【解决方案5】:

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2017-10-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-04-11
              • 1970-01-01
              • 2013-06-18
              • 1970-01-01
              相关资源
              最近更新 更多