5

8 コア プロセッサで 64 ビット Windows 7 を実行しています。私は以下を実行しました:

    #include "stdafx.h"
    #include <iostream>
    #include <Windows.h>
    #include <process.h>
    #include <ctime>

    using namespace std;

    int count = 0;
    int t = time(NULL);

    //poop() loops incrementing count until it is 300 million.
    void poop(void* params) {
        while(count < 300000000) {
            count++;
        }


        cout<< time(NULL) - t <<" \n";
    }

    int _tmain(int argc, _TCHAR* argv[])
    {
        //_beginthread(poop, 0, NULL);      
        //_beginthread(poop, 0, NULL);
        poop(NULL);

        cout<<"done"<<endl;

        while(1);

        return 0;
    }

beginThread のコメントを外したときと結果を比較しました。シングルスレッドバージョンがこれを最も速く達成することがわかりました! 実際には、スレッドを追加すると、プロセスにさらに時間がかかります。カウントを 3 億にすると、プロセスに 8 秒以上かかりました。これは、beginThread の関数呼び出しとその他の小さなオーバーヘッドを除外するのに十分であると考えました。

私は少し調査を行いましたが、マルチスレッド プロセスが遅くなる一般的な結論はオーバーヘッドです。ただし、この場合、複数のスレッドを実行するか、単一のスレッドを実行するかに関係なく、変数カウント (事前に割り当てられた変数であるため、データ セグメントに存在する) がアクセスされる回数は同じです。したがって、基本的に、オーバーヘッド (オーバーヘッドの問題である場合) は、ローカル変数よりもグローバル変数にアクセスする方がコストがかかるという事実から来ているわけではありません。

タスク マネージャーを見ると、シングル スレッドのプロセスは 13% の CPU (約 1/8 コア) を使用しており、スレッドを追加すると CPU 使用率が約 1/8 ずつ増加します。したがって、CPU パワーに関しては、タスク マネージャーがこれを正確に表していると仮定すると、スレッドを追加するとより多くの CPU が使用されます。これはさらに私を混乱させます..別のコアでより多くの全体的なCPUを使用しているのに、タスクを完了するのに全体的に時間がかかっているのはなぜですか?

TLDR: なぜこれが起こるのか

4

2 に答える 2

5

あなたのコードは本質的に間違っています。

count++値を読み取り、インクリメントしてから変数に格納する 3 段階の操作です。
2 つのスレッドがcount++同じ変数で同時に実行されると、そのうちの 1 つが他のスレッドの変更を上書きします。

したがって、マルチスレッド バージョンは、各スレッドが他のスレッドの進行状況を妨害するため、余分な作業を行うことになります。

ローカル変数を作成countすると、タイミングはより正常に見えるはずです。

または、スレッドセーフですが、スレッド間で同期するための余分なオーバーヘッドがあるインターロック インクリメントを使用することもできます。

于 2013-03-28T03:05:45.100 に答える
3

元の質問に対するコメント者の一部が指摘しているように、正確さとパフォーマンスの問題があります。まず、すべてのスレッドがカウントに同時にアクセスしています。これは、スレッドが実際にすべて3 億にカウントされるという保証がないことを意味します。poop関数内でcountを宣言することにより、この正確性のバグを解決できます。

void poop(void* params) {
    int count  = 0;
    while(count < 300000000) {
        count++;
    }
    cout<< time(NULL) - t <<" \n";
}

tはスレッドによって読み取られるだけで書き込まれないため、これは問題ではないことに注意してください。ただし、複数のスレッドからも書き込みを行っているため、 coutには問題があります。

さらに、コメントで指摘されているように、すべてのスレッドが 1 つのメモリ ロケーションにアクセスしています。これは、スレッドが更新をカウントするときに、それを保持しているキャッシュ ラインをフラッシュして再ロードする必要があることを意味します。これは非常に非効率的なメモリ アクセスです。通常、これは、単一の変数ではなく、配列内の連続した要素にアクセスしている場合に発生します (悪い考えです。上記を参照してください)。これに対する解決策は、配列をパディングして、各エントリが L1 キャッシュ ライン サイズの正確な倍数になるようにすることです。これは明らかに、ターゲット プロセッサに多少固有のものです。別のオプションは、次のいずれかになるようにアルゴリズムを再構築することです。各スレッドが連続する要素の大きなブロックを処理したり、スレッドが隣接する場所にアクセスしないように各スレッドが要素にアクセスしたりします。

Windows を使用しているため、Win32 スレッド関数ではなく、より高いレベルの抽象化をコードに使用することを検討することをお勧めします。Parallel Patterns Libraryは、ここでの法案に適合します ( Intel の Threaded Building Blocksと同様)。

    concurrency::parallel_invoke(
        [=] { poop(nullptr); },
        [=] { poop(nullptr); }
    );

これにより、アプリケーションが明示的にスレッドを作成するのではなく、PPL がスレッド プールでタスクをスケジュールできます。

また、非常に小さなタスクの場合、追加のスレッドを開始するオーバーヘッドが、並列実行による利点を上回る可能性があると考えるかもしれません。

于 2013-03-28T03:31:18.283 に答える