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

时间:2012-02-14 10:26:05

标签: c++ multithreading performance cpu cpu-usage

背景

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

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

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

问题:

我不明白为什么有些线程会占用大量CPU。

我将其分析如下:如果操作系统做出像context-switchinterruptscheduling这样的决定,那么哪个线程应该被授予对系统资源的访问权限,例如: CPU时间,那么进程中的某些线程是如何使用比其他线程更多的CPU?看起来一些线程强行从操作系统枪口获取CPU,或者操作系统对某些线程有一个真正的软点,因此它从一开始就偏向于它们,给他们所有的资源。为什么它不能公正,并平等地给予他们所有人?

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

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

我正在使用Visual Studio 2010。

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

更新

在我意识到我的消费者线程正在使用busy-spin之后,我用Sleep修复了3秒钟。此修复是暂时的,很快我将使用Event。但即使使用Sleep,CPU使用率也下降到30-40%,有时它会达到50%,从可用性的角度来看似乎并不合适,因为系统没有回应用户当前正在使用的其他应用程序。

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

8 个答案:

答案 0 :(得分:24)

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

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

  1. 阻止等待某事(睡眠,互斥,I / O等)
  2. 可运行,但当前未运行,因为其他内容
  3. 运行。
  4. 调度程序将选择要运行的内容,因为它具有内核(或超线程,无论如何),并运行每个内容,直到它阻塞或直到称为“时间片”的任意时间段到期。如果可以的话,它会安排别的东西。

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

    如何调度程序根据优先级等内容选择运行的内容。但基本的想法是,有很多事要做的线程不需要被预测为计算量很大,只要需要调度,它就会随时可用,因此会倾向于安排。

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

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

      

    如果只有一个线程将项目排入队列,那么它是否安全   正好一条线程来自它的deque项目?

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

答案 1 :(得分:7)

编辑:好的,因为您使用busy spin来阻塞队列,这很可能是导致CPU占用率过高的原因。操作系统的印象是你的线程实际上没有做有用的工作,因此它们可以获得完整的CPU时间。这里有一个有趣的讨论:Which one is better for performance to check another threads boolean in java

我建议你切换到事件或其他阻塞机制,或者使用一些同步队列,看看它是怎么回事。

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

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

答案 2 :(得分:4)

在开始考虑如何优化线程以消耗更少的CPU之前,您需要知道所花费的CPU时间在哪里。获取此信息的一种方法是使用CPU分析器。如果您没有,请尝试Very Sleepy。它易于使用,而且免费。

CPU探查器将监视正在运行的应用程序并记录时间花费的时间。因此,它将为您提供一系列函数,这些函数按照它们在采样期间使用的CPU数量,调用次数等进行排序。现在,您需要查看从大多数CPU密集型函数开始的分析结果。看看你可以改变那些以减少CPU使用率。

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

现在让我们考虑一下你可以找到消耗大量CPU的东西。

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

    您可能会发现,使用这样的设置,分配给此线程的大部分CPU时间都用于循环和检查,而实际工作的花费很少。这就是所谓的忙碌等待问题。要部分解决此问题,您可以在循环迭代之间添加sleep,但这不是最佳解决方案。解决这个问题的理想方法是在没有工作要做时让线程进入休眠状态,当一些其他线程为休眠线程生成工作时,它会发送一个信号来唤醒它。这实际上消除了循环开销,线程只在有工作时才使用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();
        }
    };
    

    请注意,在上面的解决方案中,线程函数总是在执行一个任务后尝试进入休眠状态。如果线程的队列有更多的工作项,那么信号量上的等待将立即返回,因为每次将一个项添加到队列时,发起者必须调用wake_up()函数。

  • 您在分析器输出中可能看到的另一件事是,大多数CPU花费在工作线程执行工作时执行的函数中。这实际上并不是一件坏事,如果大部分时间花在工作上,那么这意味着线程有工作要做,并且有可用的CPU时间来完成这项工作,所以原则上这里没有任何错误。

    但是,您可能不会对应用程序使用如此多的CPU感到高兴,因此您需要了解优化代码的方法,以便更有效地完成工作。

    例如,您可能会发现一些小辅助函数被调用了数百万次,因此虽然函数的单次运行很快,但如果将它乘以几百万,它就成了线程的瓶颈。此时,您应该通过优化代码或优化调用者来更少地调用函数来查看优化以减少此函数中CPU使用率的方法。

    所以这里的策略是根据分析报告从最昂贵的函数开始,并尝试进行小的优化。然后重新运行探查器以查看事情是如何变化的。您可能会发现对CPU密集型功能的一个小改动会将其降低到第二或第三位,从而降低了整体CPU使用率。在您祝贺自己的改进之后,您可以使用新的top函数重复练习。您可以继续此过程,直到您确信您的应用程序尽可能高效。

祝你好运。

答案 3 :(得分:3)

虽然其他人已经正确地分析了这个问题(据我所知),但我试着在拟议的解决方案中添加更多细节。

首先,总结一下问题: 1.如果你让你的消费者线程忙于for-loop或类似的旋转,那就太可能浪费CPU能力了。 2.如果你使用具有固定毫秒数的sleep()函数,那么它也会浪费CPU(如果时间量太低),或者你不必要地延迟进程(如果它太高)。没有办法正确设置时间量。

您需要做的事情是使用一种在恰当时刻醒来的睡眠类型,即每当新任务被附加到队列时。

我将解释如何使用POSIX执行此操作。我知道当你在Windows上时这并不理想,但是,为了从中受益,你可以使用适用于Windows的POSIX库或使用你环境中可用的相应功能。

第1步:您需要一个互斥锁和一个信号:

#include <pthread.h>
pthread_mutex_t *mutex  = new pthread_mutex_t;
pthread_cond_t  *signal = new pthread_cond_t;

/* Initialize the mutex and the signal as below.
   Both functions return an error code. If that
   is not zero, you need to react to it. I will
   skip the details of this. */
pthread_mutex_init(mutex,0);
pthread_cond_init(signal,0);

第2步:现在在消费者线程中,等待发送信号。这个想法是生产者在将新任务附加到队列时发送信号:

/* Lock the mutex. Again, this might return an error code. */
pthread_mutex_lock(mutex);

/* Wait for the signal. This unlocks the mutex and then 'immediately'
   falls asleep. So this is what replaces the busy spinning, or the
   fixed-time sleep. */
pthread_cond_wait(signal,mutex);

/* The program will reach this point only when a signal has been sent.
   In that case the above waiting function will have locked the mutex
   right away. We need to unlock it, so another thread (consumer or
   producer alike) can access the signal if needed.  */
pthread_mutex_unlock(mutex);

/* Next, pick a task from the queue and deal with it. */

上面的步骤2应该基本上放在无限循环中。确保进程有一种方法可以摆脱循环。例如 - 虽然略显粗糙 - 你可以在队列中附加一个'特殊'任务,这意味着“突破循环”。

步骤3:启用生产者线程,只要将任务附加到队列,就会发送信号:

/* We assume we are now in the producer thread and have just appended
   a task to the queue. */
/* First we lock the mutex. This must be THE SAME mutex object as used
   in the consumer thread. */
pthread_mutex_lock(mutex);

/* Then send the signal. The argument must also refer to THE SAME
   signal object as is used by the consumer. */
pthread_cond_signal(signal);

/* Unlock the mutex so other threads (producers or consumers alike) can
   make use of the signal. */
pthread_mutex_unlock(mutex);

第4步:当一切都完成并关闭你的线程时,你必须销毁互斥锁​​和信号:

pthread_mutex_destroy(mutex);
pthread_cond_destroy(signal);
delete mutex;
delete signal;

最后让我重新讨论其他人已经说过的一件事:你不能使用普通的std::deque进行并发访问。解决此问题的一种方法是声明另一个互斥锁,在每次访问双端队列之前将其锁定,然后立即解锁。

根据评论,编辑:关于制作人线程的更多话语。据我所知,生产者线程目前可以自由地向队列中添加尽可能多的任务。所以我想它会继续这样做,并保持CPU忙碌到不受IO和内存访问延迟的程度。首先,我不认为由此引起的高CPU使用率是一个问题,而是一个好处。但是,一个严重的问题是队列将无限增长,可能导致进程耗尽内存空间。因此,一个有用的预防措施是将队列的大小限制在合理的最大值,并在队列增长太长时让生产者线程暂停。

为了实现这一点,生产者线程将在添加新项之前检查队列的长度。如果它已满,它将使自己进入睡眠状态,等待消费者在从队列中取出任务时发送信号。为此,您可以使用辅助信号机制,类似于上述信号机制。

答案 4 :(得分:3)

线程消耗内存等资源。阻塞/解除阻塞线程会产生一次性成本。如果一个线程每秒阻塞/解除阻塞数万次,这可能会浪费大量的CPU。

然而,一旦某个线程被阻止,它被阻止的时间并不重要,没有持续的成本。 找到性能问题的流行方法是使用分析器。

但是,我做了很多,我的方法是:http://www.wikihow.com/Optimize-Your-Program%27s-Performance

答案 5 :(得分:0)

线程CPU使用率取决于许多因素,但主要是OS只能根据可以中断线程的点来分配处理时间。

如果您的线程无论如何都要与硬件交互,那么这会让操作系统有机会中断线程并在其他地方分配处理,这主要是基于硬件交互需要时间的假设。在您的示例中,您正在使用iostream库,从而与硬件进行交互。

如果你的循环没有这个,那么它很可能会使用接近100%的cpu。

答案 6 :(得分:0)

  1. 使用异步(文件和套接字)IO来减少无用的CPU等待时间。
  2. 使用垂直线程模型来尽可能减少上下文切换
  3. 使用无锁数据结构
  4. 使用分析工具(例如VTune)来找出热点并进行优化

答案 7 :(得分:0)

正如人们所说,在生产者和消费者线程之间同步切换的正确方法是使用条件变量。当生产者想要向队列添加元素时,它会锁定条件变量,添加元素,并在条件变量上通知服务员。使用者等待相同的条件变量,并在通知时消耗队列中的元素,然后再次锁定。我个人建议对这些使用boost :: interprocess,但也可以使用其他API以相当简单的方式完成。

另外,要记住的一点是,虽然从概念上讲每个线程只在队列的一端运行,但大多数库实现了O(1)count()方法,这意味着它们有一个成员变量跟踪元素的数量,这是罕见且难以诊断的并发问题的机会。

如果你正在寻找一种方法来减少消费者线程的CPU使用率(是的,我知道这是你真正的问题)......好吧,听起来它实际上正在做它现在应该做的事,但是数据处理很昂贵。如果您可以分析它正在做什么,可能会有优化的机会。

如果你想智能地限制生产者线程...这是一个更多的工作,但你可以让生产者线程将项目添加到队列,直到它达到某个阈值(比如10个元素),然后等待< em>不同的条件变量。当消费者消耗足够的数据导致排队元素的数量低于阈值(比如5个元素)时,它会通知第二个条件变量。如果系统的所有部分都可以快速移动数据,那么这仍然会消耗大量的CPU,但它会在它们之间相对均匀地传播。正是在这一点上,操作系统应该负责让其他不相关的进程获得其公平(ish)的CPU份额。