web-dev-qa-db-ja.com

スレッドコンテキストスイッチングのオーバーヘッドを推定する方法は?

リアルタイムの期限でスレッド化されたアプリケーションのパフォーマンスを改善しようとしています。 Windows Mobile上で実行され、C/C++で記述されています。高頻度のスレッド切り替えが目に見えるオーバーヘッドを引き起こしているかもしれないが、それを証明したり反証したりすることはできません。誰もが知っているように、証拠の欠如は反対の証拠ではありません:)。

したがって、私の質問は2つあります。

  • 存在する場合、スレッドコンテキストを切り替えるコストの実際の測定値はどこにありますか?

  • テストアプリケーションの作成に時間を費やすことなく、既存のアプリケーションのスレッドスイッチングオーバーヘッドを推定する方法は何ですか?

  • 誰もが特定のスレッドのコンテキストスイッチの数(オン/オフ)を見つける方法を知っていますか?

57

あなたはテストアプリケーションを書きたくないと言っていましたが、オーバーヘッドが何であるかを知るために、ARM9 Linuxプラットフォームでの以前のテストでこれを行いました。 :: thread :: yield()(または、知っている)をブーストし、変数をインクリメントするのはたった2つのスレッドでした。 1秒あたりに実行できるコンテキストスイッチの数。もちろん、これは実際には正確ではありませんが、ポイントは両方のスレッドが相互にCPUを譲り、それが非常に高速だったため、オーバーヘッドについて考えることはもはや意味をなさないことです。したがって、存在しない可能性のある問題について考えすぎずに、単純に先に進み、単純なテストを作成するだけです。

それ以外にも、パフォーマンスカウンターで推奨される1800のように試すことができます。

ああ、Windows CE 4.Xで実行しているアプリケーションを覚えています。このアプリケーションでは、4つのスレッドが集中的に切り替えられることがあり、パフォーマンスの問題に遭遇することはありませんでした。また、スレッドなしでコアスレッド処理を実装しようとしましたが、パフォーマンスの改善は見られませんでした(GUIの応答は非常に遅くなりましたが、他のすべては同じでした)。コンテキストスイッチの数を減らすか、スレッドを完全に削除する(テストのために)ことで、同じことを試すことができます。

13
OregonGhost

既存のプラットフォームのWeb上のどこかに、このオーバーヘッドを見つけることができるとは思いません。あまりにも多くの異なるプラットフォームが存在します。オーバーヘッドは2つの要因に依存します。

  • CPU。さまざまなCPUタイプで必要な操作が簡単または難しい場合があるため
  • 異なるカーネルは各スイッチで異なる操作を実行する必要があるため、システムカーネル

他の要因には、切り替えが行われる方法が含まれます。切り替えは次の場合に実行できます

  1. スレッドはそのタイムクォンタムをすべて使用しました。スレッドが開始されると、次のユーザーを決定するカーネルに制御を戻す必要がある前に、一定の時間実行される場合があります。

  2. スレッドは横取りされました。これは、別のスレッドがCPU時間を必要とし、優先度が高い場合に発生します。例えば。マウス/キーボード入力を処理するスレッドは、そのようなスレッドである可能性があります。どのスレッドowns CPUであっても、ユーザーが何かを入力するかクリックすると、現在のスレッドのタイムクォンタムが完全に使い果たされるまで待ちたくないので、システムはすぐに反応します。したがって、一部のシステムは、現在のスレッドをすぐに停止させ、優先順位の高い他のスレッドに制御を戻します。

  3. スレッドはCPU時間を必要としません。これは、スレッドが何らかの操作をブロックするか、単にsleep()(または同様の)を呼び出して実行を停止するためです。

これらの3つのシナリオでは、理論的にはスレッドの切り替え時間が異なる場合があります。例えば。 sleep()の呼び出しはCPUがカーネルに戻されることを意味し、カーネルは約1時間後にウェイクアップコールをセットアップする必要があることを意味するため、最後のものが最も遅いと予想します。スリープするよう要求した時間、スレッドをスケジューリングプロセスから外し、スレッドが起こされたら、スレッドをスケジューリングプロセスに再度追加する必要があります。これらのすべての急勾配は、ある程度の時間がかかります。したがって、実際のスリープ呼び出しは、別のスレッドに切り替えるのにかかる時間よりも長くなる可能性があります。

確実に知りたい場合は、ベンチマークを行う必要があると思います。問題は、通常、スレッドをスリープ状態にするか、ミューテックスを使用してスレッドを同期する必要があることです。ミューテックスのスリープまたはロック/ロック解除には、それ自体にオーバーヘッドがあります。つまり、ベンチマークにはこれらのオーバーヘッドも含まれます。強力なプロファイラーがなければ、実際の切り替えにどれだけのCPU時間を使用し、スリープ/ミューテックスコールにどれだけのCPU時間を使用したかを後から言うのは困難です。一方、実際のシナリオでは、スレッドはスリープするか、ロックを介して同期します。コンテキスト切り替え時間を純粋に測定するベンチマークは、実際のシナリオをモデル化しないため、総合的なベンチマークです。実際のシナリオに基づいている場合、ベンチマークははるかに「現実的」です。実際の3Dアプリケーションでこの結果を達成できない場合、GPUが理論的には1秒あたり20億ポリゴンを処理できることを示すGPUベンチマークはどのような用途ですか?実生活の3DアプリケーションでGPUが1秒間に処理できるポリゴンの数を知るのは、もっと面白いと思いませんか?

残念ながら、Windowsプログラミングについては何も知りません。 Windows用のアプリケーションをJavaまたはC#で作成できますが、Windows上のC/C++で泣きそうになります。POSIXのソースコードしか提供できません。

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>

uint32_t COUNTER;
pthread_mutex_t LOCK;
pthread_mutex_t START;
pthread_cond_t CONDITION;

void * threads (
    void * unused
) {
    // Wait till we may fire away
    pthread_mutex_lock(&START);
    pthread_mutex_unlock(&START);

    pthread_mutex_lock(&LOCK);
    // If I'm not the first thread, the other thread is already waiting on
    // the condition, thus Ihave to wake it up first, otherwise we'll deadlock
    if (COUNTER > 0) {
        pthread_cond_signal(&CONDITION);
    }
    for (;;) {
        COUNTER++;
        pthread_cond_wait(&CONDITION, &LOCK);
        // Always wake up the other thread before processing. The other
        // thread will not be able to do anything as long as I don't go
        // back to sleep first.
        pthread_cond_signal(&CONDITION);
    }
    pthread_mutex_unlock(&LOCK); //To unlock
}

int64_t timeInMS ()
{
    struct timeval t;

    gettimeofday(&t, NULL);
    return (
        (int64_t)t.tv_sec * 1000 +
        (int64_t)t.tv_usec / 1000
    );
}


int main (
    int argc,
    char ** argv
) {
    int64_t start;
    pthread_t t1;
    pthread_t t2;
    int64_t myTime;

    pthread_mutex_init(&LOCK, NULL);
    pthread_mutex_init(&START, NULL);   
    pthread_cond_init(&CONDITION, NULL);

    pthread_mutex_lock(&START);
    COUNTER = 0;
    pthread_create(&t1, NULL, threads, NULL);
    pthread_create(&t2, NULL, threads, NULL);
    pthread_detach(t1);
    pthread_detach(t2);
    // Get start time and fire away
    myTime = timeInMS();
    pthread_mutex_unlock(&START);
    // Wait for about a second
    sleep(1);
    // Stop both threads
    pthread_mutex_lock(&LOCK);
    // Find out how much time has really passed. sleep won't guarantee me that
    // I sleep exactly one second, I might sleep longer since even after being
    // woken up, it can take some time before I gain back CPU time. Further
    // some more time might have passed before I obtained the lock!
    myTime = timeInMS() - myTime;
    // Correct the number of thread switches accordingly
    COUNTER = (uint32_t)(((uint64_t)COUNTER * 1000) / myTime);
    printf("Number of thread switches in about one second was %u\n", COUNTER);
    return 0;
}

出力

Number of thread switches in about one second was 108406

ロックと条件付きの待機があるにもかかわらず、100,000以上はそれほど悪くありません。このようなものがなければ、1秒間に少なくとも2倍のスレッド切り替えが可能だったと思います。

26
Mecki

推定できません。あなたはそれを測定する必要があります。そして、それはデバイスのプロセッサによって異なるでしょう。

コンテキストスイッチを測定するには、2つの非常に簡単な方法があります。 1つにはコードが含まれ、もう1つには含まれません。

最初に、コードの方法(擬似コード):

DWORD tick;

main()
{
  HANDLE hThread = CreateThread(..., ThreadProc, CREATE_SUSPENDED, ...);
  tick = QueryPerformanceCounter();
  CeSetThreadPriority(hThread, 10); // real high
  ResumeThread(hThread);
  Sleep(10);
}

ThreadProc()
{
  tick = QueryPerformanceCounter() - tick;
  RETAILMSG(TRUE, (_T("ET: %i\r\n"), tick));
}

明らかにループでそれを行い、平均化する方が良いでしょう。これはコンテキストスイッチを測定するだけではないことに注意してください。また、ResumeThreadの呼び出しを測定しており、スケジューラーがすぐに他のスレッドに切り替わるという保証はありません(ただし、優先順位10は、オッズの増加に役立つはずです)。

スケジューライベントにフックすることにより、CeLogを使用してより正確な測定値を取得できますが、それは簡単なことではなく、十分に文書化されていません。もしあなたが本当にその道を行きたいのなら、スー・ローには検索エンジンが見つけられるいくつかのブログがあります。

非コードルートは、リモートカーネルトラッカーを使用することです。 eVC 4.0または評価版のPlatform Builderをインストールして入手してください。カーネルが実行しているすべてをグラフィカルに表示し、提供されたカーソル機能を使用してスレッドコンテキストスイッチを直接測定できます。繰り返しになりますが、SueにはKernel Trackerの使用に関するブログエントリもあります。

とはいえ、CEプロセス内のスレッドコンテキストスイッチは、非常に高速であることがわかります。 RAMでアクティブなプロセスを交換してから移行を行う必要があるため、高価なのはプロセススイッチです。

14
ctacke

My C++の50行 Linux(QuadCore Q6600)のコンテキスト切り替え時間〜0.9us(2スレッドで0.75us、50スレッドで0.95)を表示します。このベンチマークでは、スレッドは時間のクォンタムを取得するとすぐにyieldを呼び出します。

7
bobah

コンテキストスイッチは高価であり、経験則として30µsのCPUオーバーヘッドがかかります http://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context。 html

6
Soroush

私はこれを一度だけ推定しようとしましたが、それは486でした!結果は、プロセッサコンテキストスイッチが完了するまでに約70命令を必要としていたことです(これは、多くのOS API呼び出しとスレッドスイッチングで発生していたことに注意してください)。 DX3では、スレッドスイッチごとに約30us(OSオーバーヘッドを含む)かかっていると計算しました。 1秒間に行っていた数千のコンテキスト切り替えは、プロセッサ時間の5〜10%を吸収していました。

それがどのようにマルチコア、マルチGHzの最新のプロセッサに変換されるかはわかりませんが、スレッドの切り替えでオーバーヘッドを完全に超えない限り無視できると思います。

スレッドの作成/削除は、スレッドのアクティブ化/非アクティブ化よりも高価なCPU/OSであることに注意してください。スレッドの多いアプリの適切なポリシーは、スレッドプールを使用し、必要に応じてアクティブ化/非アクティブ化することです。

5
Tim Ring

コンテキストスイッチの問題は、スイッチの時間が固定されていることです。 GPUが実装したスレッド間の1サイクルコンテキストスイッチ。たとえば、次のものはCPUでスレッド化できません。

double * a; 
...
for (i = 0; i < 1000; i ++)
{
    a[i] = a[i] + a[i]
}

実行時間がコンテキスト切り替えコストよりもはるかに短いためです。 Core i7では、このコードは約1マイクロ秒かかります(コンパイラーに依存します)。したがって、コンテキスト切り替え時間は、小さなジョブをスレッド化する方法を定義するため重要です。これは、コンテキストスイッチを効果的に測定する方法も提供すると思います。スレッドプールの2つのスレッドが、単一のスレッドのスレッドと比較して実際の利点を示すように、配列の長さ(上の例)を確認します。これは簡単に100,000要素になる可能性があるため、有効なコンテキスト切り替え時間は同じアプリ内で20usの範囲内になります。

スレッドプールで使用されるすべてのカプセル化は、スレッドの切り替え時間にカウントする必要があります。これは、すべて(最終的に)の結果であるためです。

アトマプリ

3
Atmapuri

コンテキストスイッチは非常に高価です。 CPUの動作自体ではなく、キャッシュの無効化が原因です。集中的なタスクを実行している場合、命令とデータの両方でCPUキャッシュがいっぱいになり、メモリプリフェッチ、TLB、およびRAMはRAMの一部の領域に対する作業を最適化します。

コンテキストを変更すると、これらのキャッシュメカニズムはすべてリセットされ、新しいスレッドは「空白」状態から開始されます。

スレッドが単にカウンターをインクリメントしていない限り、受け入れられた答えは間違っています。もちろん、この場合にはキャッシュフラッシュは関係しません。実際のアプリケーションのようにキャッシュをいっぱいにせずにコンテキストスイッチングをベンチマークしても意味がありません。

1
bokan

わかりませんが、Windows Mobileに通常のパフォーマンスカウンターはありますか?コンテキストスイッチ/秒などを見ることができます。ただし、コンテキスト切り替え時間を具体的に測定するものがあるかどうかはわかりません。

1