web-dev-qa-db-ja.com

CPU使用率が高い一般的な原因は何ですか?

背景:

C++で記述されたアプリケーションでは、次の3つのスレッドを作成しました。

  • AnalysisThread(またはプロデューサー):入力ファイルを読み取り、解析してパターンを生成し、それらをstd::queueにキューに入れます。1
  • PatternIdRequestThread(またはコンシューマー):キューからパターンをデキューし、クライアント(C++で記述)を介してデータベースに1つずつ送信します。クライアントはパターン uid を返します。対応するパターンに割り当てられます。
  • ResultPersistenceThread:CPU使用率に関する限り、それはさらにいくつかのことを行い、データベースと通信し、期待どおりに正常に動作します。

最初の2つのスレッドはCPU使用率の60〜80%を占め、それぞれが平均で35%を占めます。

質問:

一部のスレッドがCPU使用率を高くする理由がわかりません。

私はそれを次のように分析します: context-switchinterruptscheduling のような決定を行うのがOSの場合、どのスレッドを使用するかについてCPU時間などのシステムリソースへのアクセスが与えられた場合、プロセス内の一部のスレッドが他のスレッドよりも多くのCPUを使用するのはなぜですか? 一部のスレッドがOSからCPUを強制的にガンポイントで取得しているように見えます。または、OSに一部のスレッドの実際のソフトスポットがあるため、最初から彼らに偏っており、彼らが持っているすべてのリソースを彼らに与えています。なぜそれは公平であり、それらすべてを平等に与えることができないのですか?

私はそれが素朴であることを知っています。しかし、この線に沿って考えると、さらに混乱します。OSは、スレッドによって実行される作業量に基づいて、スレッドへのCPUへのアクセスを許可しますが、OSはどのように作業量を計算または予測しますか before 完全に実行しますか?

CPU使用率が高い原因は何ですか?どうすればそれらを識別できますか?コードを見ただけで識別できますか?ツールは何ですか?

Visual Studio2010を使用しています。

1. std::queueについても疑問があります。標準のコンテナはスレッドセーフではないことを私は知っています。しかし、ちょうど1つのスレッドがアイテムをキューにエンキューする場合、ちょうど1つのスレッドがアイテムをデキューしても安全ですか?パイプのようなものだと思います。一方の側でデータを挿入し、もう一方の側でデータを削除します。それを同時に実行すると、なぜ安全ではないのでしょうか。しかし、それはこのトピックの本当の質問ではありませんが、これに対処するために、回答にメモを追加することができます。

更新:

コンシューマスレッドがbusy-spinを使用していることに気付いた後、これを Sleep で3秒間修正しました。この修正は一時的なものであり、すぐに代わりに Event を使用します。しかし、 Sleep の場合でも、CPU使用率は30〜40%に低下し、場合によっては50%に上昇します。これは、システムとしての使いやすさの観点からは望ましくないようです。ユーザーが現在使用している他のアプリケーションには応答しません。

CPU使用率が高い場合でも改善できる方法はありますか?前に述べたように、プロデューサースレッド(現在はほとんどのCPUサイクルを使用しています)はファイルを読み取り、その中のパケット(何らかの形式)を解析し、それらからパターンを生成します。スリープを使用すると、CPU使用率が低下しますが、それは良い考えですか?それを解決する一般的な方法は何ですか?

16
Nawaz

個人的には、スレッドに作業が必要な場合はかなりイライラします。また、OS ではなかったでCPU使用率が高いため、マシンにアイドル状態のコアがありました。したがって、ここで問題が発生していることは実際にはわかりません[編集:ビジーループが問題であることが判明しましたが、原則として、CPU使用率が高くても問題はありません]。

OS /スケジューラーは、スレッドが実行する作業量をほとんど予測していません。スレッドは(過度に単純化するために)次の3つの状態のいずれかになります。

  1. 何か(スリープ、ミューテックス、I/Oなど)の待機がブロックされました
  2. 実行可能ですが、他のものがあるため、現在実行されていません
  3. ランニング。

スケジューラーは、コア(またはハイパースレッドなど)と同じ数の実行対象を選択し、ブロックするか、「タイムスライス」と呼ばれる任意の期間が経過するまで、それぞれを実行します。次に、可能であれば、別のスケジュールを設定します。

したがって、スレッドがブロックではなく計算にほとんどの時間を費やし、コアが空いている場合、スレッドは多くのCPU時間を占有します。

howには多くの詳細があります。スケジューラーは、優先順位などに基づいて、実行するものを選択します。しかし、基本的な考え方は、やることがたくさんあるスレッドを計算量が多いと予測する必要はなく、何かをスケジュールする必要があるときはいつでも利用できるため、スケジュールされる傾向があるということです。

サンプルループの場合、コードは実際には何も実行しないため、5〜7%のCPUが適切かどうかを判断する前に、コードがどのように最適化されているかを確認する必要があります。理想的には、2コアマシンでは、処理量の多いスレッドshouldが50%のCPUを占有します。 4コアマシンでは、25%。したがって、少なくとも16コアがない限り、結果は一見異常です(16コアがある場合、35%を占める1つのスレッドはさらに異常になります!)。標準のデスクトップOSでは、ほとんどのコアがほとんどの時間アイドル状態であるため、実際のプログラムが実行時に占有するCPUの割合が高いほど優れています。

私のマシンでは、ほとんどがテキストを解析しているコードを実行するときに、1コア分のCPU使用量に頻繁に遭遇します。

ちょうど1つのスレッドがアイテムをキューにエンキューする場合、ちょうど1つのスレッドがアイテムをデキューしても安全ですか?

いいえ、それは標準コンテナのstd::queueにとって安全ではありません。 std::queueは、シーケンスコンテナ(vectordequeまたはlist)の上にある薄いラッパーであり、スレッドセーフを追加しません。アイテムを追加するスレッドとアイテムを削除するスレッドは、いくつかの共通のデータを変更します。たとえば、基になるコンテナーのsizeフィールドです。同期が必要です。そうでない場合は、共通データへのアトミックアクセスに依存する安全なロックフリーキュー構造が必要です。 std::queueにはどちらもありません。

24
Steve Jessop

編集:わかりました。キューでブロックするためにビジースピンを使用しているため、これがCPU使用率の高さの原因である可能性があります。 OSは、スレッドが実際にはそうではないのに有用な作業を行っているという印象を受けているため、CPU時間がフルになります。ここで興味深い議論がありました: Javaでブール値の別のスレッドをチェックするためにパフォーマンスに優れているのはどれですか

イベントまたは他のブロックメカニズムに切り替えるか、代わりに同期キューを使用して、それがどのように行われるかを確認することをお勧めします。

また、キューがスレッドセーフであるという理由は、「2つのスレッドのみが使用しているため」非常に危険です。

キューがリンクリストとして実装されていると仮定して、残りの要素が1つまたは2つしかない場合に何が起こるか想像してみてください。プロデューサーとコンシューマーの相対速度を制御する方法がないため、これが当てはまる可能性があり、大きな問題に直面しています。

7
Tudor

スレッドを最適化してCPUの消費量を減らす方法を考える前に、CPU時間が費やされている場所を把握しておく必要があります。この情報を取得する1つの方法は、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();
        }
    };
    

    上記のソリューションでは、スレッド関数は常に1つのタスクを実行した後にスリープ状態になろうとすることに注意してください。スレッドのキューにさらに多くの作業項目がある場合、項目がキューに追加されるたびに発信者がwake_up()関数を呼び出している必要があるため、セマフォの待機はすぐに戻ります。

  • プロファイラーの出力に表示されるもう1つの点は、CPUのほとんどが、作業中にワーカースレッドによって実行される関数に費やされていることです。これは実際には悪いことではありません。ほとんどの時間が作業に費やされている場合、それはスレッドに作業があり、その作業に使用できるCPU時間があったことを意味します。したがって、原則としてここでは問題はありません。

    しかし、それでも、アプリケーションがそれほど多くのCPUを使用することに満足できない場合があるため、コードを最適化して作業をより効率的に行う方法を検討する必要があります。

    たとえば、いくつかの小さな補助関数が何百万回も呼び出されたことがわかる場合があります。そのため、関数の1回の実行は高速ですが、それを数百万倍すると、スレッドのボトルネックになります。この時点で、コードを最適化するか、呼び出し元が関数を呼び出す回数を減らすように最適化することにより、この関数のCPU使用率を減らすための最適化を行う方法を検討する必要があります。

    したがって、ここでの戦略は、プロファイリングレポートによると、最も高価な関数から開始し、小さな最適化を試みることです。次に、プロファイラーを再実行して、状況がどのように変化したかを確認します。最もCPUを集中的に使用する関数を少し変更すると、2位または3位に移動し、その結果、全体的なCPU使用率が低下することがあります。改善を祝福した後、新しいトップ機能を使用して演習を繰り返します。アプリケーションが可能な限り効率的であることに満足するまで、このプロセスを続けることができます。

幸運を。

5
Miguel

スレッドはメモリなどのリソースを消費します。スレッドのブロック/ブロック解除には、1回限りのコストが発生します。スレッドが1秒間に数万回ブロック/ブロック解除する場合、これはかなりの量のCPUを浪費する可能性があります。

ただし、スレッドがブロックされると、ブロックされている期間は関係なく、継続的なコストは発生しません。パフォーマンスの問題を見つける一般的な方法は、プロファイラーを使用することです。

しかし、私はこれを頻繁に行い、私の方法は次のとおりです。 http://www.wikihow.com/Optimize-Your-Program%27s-Performance

3
Avinash

他の人はすでに問題を正しく分析していますが(私が知る限り)、提案された解決策にもう少し詳細を追加してみましょう。

まず、問題を要約すると、次のようになります。1。コンシューマースレッドをforループなどで回転させるのに忙しい場合、それはCPUパワーのひどい浪費です。 2.固定ミリ秒数でsleep()関数を使用する場合、CPUの浪費でもあるか(時間が短すぎる場合)、プロセスを不必要に遅らせる(長すぎる場合)。時間量を正しく設定する方法はありません。

代わりに行う必要があるのはちょうどいいタイミングで、つまり新しいタスクがに追加されたときにウェイクアップするタイプのスリープを使用することです。キュー。

POSIXを使用してこれを行う方法を説明します。これはWindowsを使用している場合は理想的ではないことを認識していますが、その恩恵を受けるには、Windows用のPOSIXライブラリを使用するか、環境で使用可能な対応する機能を使用できます。

ステップ1: 1つのミューテックスと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;

最後に、他の人がすでに言ったことの1つを繰り返します。同時アクセスに通常のstd::dequeを使用してはなりません。これを解決する1つの方法は、さらに別のミューテックスを宣言し、両端キューにアクセスするたびにロックし、直後にロックを解除することです。

編集:プロデューサースレッドに関するもう少しの言葉コメントに照らして。私が理解している限り、プロデューサースレッドは現在、キューにできるだけ多くのタスクを自由に追加できます。したがって、それを継続し、IOおよびメモリアクセスによって遅延されない範囲でCPUをビジー状態に保つと思います。まず、CPU使用率が高くなるとは思いません。これは問題としてではなく、メリットとしてです。ただし、重大な懸念の1つは、キューが無期限に大きくなり、プロセスのメモリスペースが不足する可能性があることです。したがって、次の予防策として、サイズを制限することをお勧めします。妥当な最大数までキューに入れ、キューが長くなりすぎるとプロデューサースレッドを一時停止します。

これを実装するために、プロデューサースレッドは、新しいアイテムを追加する前にキューの長さをチェックします。いっぱいになると、タスクをキューから外すときにコンシューマーからシグナルが送信されるのを待って、スリープ状態になります。このために、上記のものと同様の二次信号メカニズムを使用できます。

3
jogojapan

スレッドのCPU使用率は多くの要因に依存しますが、主にOSは、スレッドを中断できるポイントに基づいて処理時間を割り当てることしかできません。

とにかくスレッドがハードウェアと相互作用する場合、これにより、主にハードウェアの相互作用に時間がかかるという仮定に基づいて、OSがスレッドを中断し、他の場所で処理を割り当てる機会が与えられます。あなたの例では、iostreamライブラリを使用しているため、ハードウェアと対話しています。

ループにこれがない場合は、ほぼ100%のCPUを使用する可能性があります。

0
ChrisBD

人々が言っ​​ているように、プロデューサースレッドとコンシューマースレッドの間のハンドオフを同期する正しい方法は、条件変数を使用することです。プロデューサーがキューに要素を追加する場合、条件変数をロックし、要素を追加して、条件変数についてウェイターに通知します。コンシューマーは同じ条件変数で待機し、通知されると、キューから要素を消費してから、再びロックします。これらにはboost :: interprocessを使用することを個人的にお勧めしますが、他のAPIを使用することもかなり簡単な方法で実行できます。

また、概念的には各スレッドがキューの一方の端でのみ動作しているのに対し、ほとんどのライブラリはO(1) count()メソッドを実装していることを覚えておいてください。つまり、要素の数を追跡するためのメンバー変数があり、これは、まれで診断が難しい同時実行の問題の機会です。

コンシューマースレッドのCPU使用率を削減する方法を探している場合(はい、これがあなたの本当の質問だと思います)...まあ、それは実際に今のはずのことをしているように聞こえますが、データ処理は高価な。それが何をしているのかを分析できれば、最適化の機会があるかもしれません。

プロデューサースレッドをインテリジェントにスロットルしたい場合...もう少し手間がかかりますが、プロデューサースレッドに特定のしきい値(たとえば10要素)に達するまでキューにアイテムを追加させてから、異なる条件変数。コンシューマーが十分なデータを消費して、キューに入れられた要素の数がしきい値(たとえば、5要素)を下回ると、この2番目の条件変数に通知します。システムのすべての部分がデータをすばやく移動できる場合でも、これは多くのCPUを消費する可能性がありますが、それらの間で比較的均等に分散されます。この時点で、OSは、他の無関係なプロセスがCPUの公平な(っぽい)シェアを取得できるようにする責任を負う必要があります。

0
bdow
  1. 非同期(ファイルとソケット)IOを使用して、無駄なCPU待機時間を削減します。
  2. 可能であれば、垂直スレッドモデルを使用してコンテキストスイッチを減らします
  3. ロックレスデータ構造を使用する
  4. vTuneなどのプロファイリングツールを使用して、ホットスポットを特定し、最適化を行います
0
BruceAdi