高CPU使用率的常见原因是什么?

Naw*_*waz 16 c++ cpu performance multithreading cpu-usage

背景:

在我用C++编写的应用程序中,我创建了3个线程:

  • AnalysisThread(或Producer):它读取输入文件,解析它并生成模式,并将它们排入std::queue1.
  • PatternIdRequestThread(或Consumer):它从队列中取消deque模式,并通过客户端(用C++编写)将它们一个接一个地发送到数据库,该客户端返回模式uid,然后将其分配给相应的模式.
  • ResultPersistenceThread:它做了更多的事情,与数据库对话,并且就CPU使用情况而言,它可以正常工作.

前两个线程占CPU使用率的60-80%,平均每个占35%.

题:

我不明白为什么有些线程占用高CPU.

我将其分析如下:如果操作系统做出决策,如上下文切换,中断调度,哪个线程应该被授予对系统资源的访问权限,比如CPU时间,那么进程中的某些线程是如何发生的呢?比其他人使用更多的CPU?看起来有些线程在枪口强行从操作系统获取CPU ,或者操作系统对某些线程有一个真正的软点,所以它从一开始就偏向于它们,为它们提供了所有的资源.为什么它不能公正,并平等地给予他们所有人?

我知道这很天真.但是,如果我按照这一思路思考,我会更加困惑:操作系统根据线程要完成的工作量来访问线程的CPU,但操作系统执行之前如何计算或预测工作量彻底?

我想知道高CPU使用率的原因是什么?我们如何识别它们?是否可以通过查看代码来识别它们?有什么工具?

我正在使用Visual Studio 2010.

我也怀疑std::queue.我知道标准容器不是线程安全的.但是如果只有一个线程将项目排队到队列中,那么如果只有一个线程从中取消项目是否安全呢?我想它就像一个管道,一方面你插入数据,另一方面,你删除数据,那么为什么它是不安全的,如果它同时完成?但是,这不是本主题中的真正问题,但是,您可以在答案中添加注释,解决此问题.

更新:

在我意识到我的消费者线程正在使用busy-spin之后,我已经使用Sleep修复了3秒钟.这个修复是暂时的,很快我就会使用Event.但即使使用Sleep,CPU的使用率也降至30-40%,有时它会达到50%,从可用性的角度来看似乎并不理想,因为系统不响应其他应用程序用户当前正在使用的.

有什么方法我仍然可以改善高CPU使用率?如前所述,生产者线程(现在使用大多数CPU周期)读取文件,解析其中的数据包(某些格式),并从中生成模式.如果我使用睡眠,那么CPU使用率会降低,但这是个好主意吗?解决它的常用方法有哪些?

Ste*_*sop 24

就个人而言,如果我的线程有工作要做,我会非常恼火,并且我的机器上有空闲内核,因为操作系统没有给它们高CPU使用率.所以我真的没有看到这里有任何问题[编辑:结果你的繁忙循环是一个问题,但原则上CPU使用率没有任何问题].

操作系统/调度程序几乎不能预测线程的工作量.线程是(过度简化)三种状态之一:

  1. 阻止等待某事(睡眠,互斥,I/O等)
  2. runnable,但当前没有运行,因为其他的东西
  3. 运行.

调度程序将选择运行的内容,因为它具有内核(或超线程,无论如何),并运行每个内容,直到它阻塞或直到称为"时间片"的任意时间段到期.如果可以的话,它会安排其他事情.

因此,如果一个线程将大部分时间花在计算而不是阻塞上,并且如果有一个可用的核心,那么它将占用大量的CPU时间.

基于优先级之类的事情,调度程序如何选择运行的内容有很多细节.但是基本的想法是,有很多事要做的线程不需要被预测为计算量很大,只要需要调度,它就会始终可用,因此往往会被安排.

对于您的示例循环,您的代码实际上并没有做任何事情,因此在判断5-7%CPU是否有意义之前,您需要检查它的优化方式.理想情况下,在双核机器上,处理繁重的线程应该占用50%的CPU.在4核机器上,25%.因此,除非你有至少16个核心,否则你的结果乍一看是异常的(如果你有16个核心,那么占用35%的一个线程会更加异常!).在标准桌面操作系统中,大多数内核大多数时间处于空闲状态,因此实际程序运行时占用的CPU比例越高越好.

在我的机器上,当我运行主要解析文本的代码时,我经常使用一个核心的CPU.

如果只有一个线程将项目排入队列,那么如果只有一个线程从中取消项目是否安全?

不,std::queue使用标准容器不安全.std::queue是一个序列容器(vector,dequelist)顶部的薄包装器,它不添加任何线程安全性.添加项的线程和删除项的线程会修改一些共同的数据,例如size底层容器的字段.您需要一些同步,或者需要一个安全的无锁队列结构,该结构依赖于对公共数据的原子访问.std::queue既没有.

  • @Nawaz:嗯,通常最好的"剖析"形式是"堆叠镜头".中断调试器中的程序并查看您的位置(堆栈跟踪中的任何位置,代码结束并且Citrix的代码开始).这样做5次,在你最终两次的任何地方都是热门代码,弄清楚你认为你的程序(和那个特定的线程)花费所有时间的代码是否合理. (2认同)
  • 低线程优先级通常不会产生任何影响.你的程序正在做很多"事情".低优先级只是意味着如果操作系统还有其他任何操作,那么它将安排它.这可能会使您的计算机更具响应性,但它​​无法解决潜在的问题,即您的程序正在做的事情超出您的想象.你需要找出它正在做什么"某事",以及它是否应该少做一些.你已经淘汰了繁忙的循环(至少,我认为你已经有Tudor告诉你这很糟糕),所以你需要找到下一个问题. (2认同)

Tud*_*dor 7

编辑:好的,因为您使用busy spin来阻塞队列,这很可能是导致CPU使用率过高的原因.操作系统的印象是你的线程实际上没有做有用的工作,因此它们可以获得完整的CPU时间.这里有一个有趣的讨论:哪一个更好的性能来检查java中的另一个线程boolean

我建议你切换到事件或其他阻塞机制或使用一些同步队列,看看它是如何进行的.

此外,关于队列是线程安全的"因为只有两个线程正在使用它"的推理非常危险.

假设队列是作为链表实现的,想象如果它只剩下一个或两个元素会发生什么.由于你无法控制生产者和消费者的相对速度,情况可能就是这样,所以你遇到了大麻烦.

  • TBB concurrent_bounded_queue有一个阻塞pop()方法,可以避免忙等待.或者,您可以将非阻塞concurrent_queue与关联的信号量一起使用来计数/等待. (3认同)

Mig*_*uel 5

在开始考虑如何优化线程以减少CPU消耗之前,需要了解所有CPU时间花费在哪里。获取此信息的一种方法是使用CPU分析器。如果您没有,请尝试“ 非常困倦”。它易于使用且免费。

CPU事件探查器将监视您正在运行的应用程序,并记下花费的时间。结果,它将为您提供函数列表,这些函数按采样期间使用的CPU数量,调用的次数等进行排序。现在,您需要查看从CPU占用最大的函数开始的概要分析结果,查看您可以更改哪些内容以减少CPU使用率。

重要的是,一旦有了探查器结果,您就会拥有实际的数据,这些数据可以告诉您可以优化应用程序的哪些部分以获得最大的回报。

现在,让我们考虑一下您会发现哪些消耗大量CPU的事情。

  • 工作线程通常实现为循环。在循环的顶部,进行检查以确定是否有工作要做,是否执行了任何可用的工作。循环的新迭代再次开始循环。

    您可能会发现,使用这种设置,分配给该线程的大部分CPU时间都花在循环和检查上,而实际上却很少花在做工作上。这就是所谓的繁忙等待问题。要部分解决此问题,您可以sleep在循环迭代之间添加in,但这不是最佳解决方案。解决此问题的理想方法是在没有工作要做时使线程进入睡眠状态,并且当某个其他线程为睡眠线程生成工作时,它将发送信号以唤醒它。这实际上消除了循环开销,线程仅在有工作要做时才使用CPU。我通常使用信号量来实现此机制,但是在Windows上,您也可以使用Event对象。这是一个实现的草图:

    class MyThread {
    private:
        void thread_function() {
            while (!exit()) {
                if (there_is_work_to_do())
                    do_work();
                go_to_sleep();
            }
        }
        // this is called by the thread function when it
        // doesn't have any more work to do
        void go_to_sleep() {
            sem.wait();
        }
    public:
        // this is called by other threads after they add work to
        // the thread's queue
        void wake_up() {
            sem.signal();
        }
    };
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,在上述解决方案中,线程函数在执行一项任务后始终尝试进入睡眠状态。如果线程的队列中有更多的工作项,则等待信号量将立即返回,因为每次将项目添加到队列中时,始发者都必须调用了ake_up()函数。

  • 您可能在探查器输出中看到的另一件事是,大多数CPU都在工作线程执行工作时花在了工作线程执行的功能上。这实际上不是一件坏事,如果大部分时间都花在工作上,那么这意味着线程有工作要做,并且有足够的CPU时间来完成该工作,因此,原则上讲,这里没有什么错。

    但是,您仍然可能不满意您的应用程序使用了如此多的CPU,因此您需要寻找优化代码的方法,以使其更有效地完成工作。

    例如,您可能会发现一些小的辅助函数被调用了数百万次,因此,尽管一次运行该函数很快,但是如果将其乘以几百万,它将成为线程的瓶颈。在这一点上,您应该研究通过优化其代码或优化调用程序以减少调用该函数次数的方式来优化以减少此函数中的CPU使用率的方法。

    因此,这里的策略是根据分析报告从最昂贵的功能开始,然后尝试进行小的优化。然后,您重新运行探查器以查看情况如何变化。您可能会发现,对CPU占用最大的功能进行小的更改会将其移至第二或第三位,结果降低了总体CPU使用率。祝贺您改进后,您可以使用新的top功能重复该练习。您可以继续执行此过程,直到对应用程序的效率感到满意为止。

祝好运。