20

複雑な配列処理タスクを複数のスレッドに分割して、マルチコア処理を利用したところ、大きなメリットが得られました。現在、タスクの開始時にスレッドを作成し、スレッドが作業を完了して終了するのを待ちます。私は通常、コアの数の約 4 倍のスレッドを作成しています。これは、スレッドごとに時間がかかる傾向があり、余分なスレッドを使用することで、ほとんどの時間すべてのコアが占有されたままになるためです。プログラムの起動時にスレッドを作成し、必要になるまでスレッドをアイドル状態に保ち、処理を開始するときにそれらを使用することで、パフォーマンス上の多くの利点があるのではないかと考えていました。もっと簡単に言えば、スレッド内の処理を超えて、新しいスレッドを開始して終了するのにどれくらいの時間がかかりますか? 現在、次を使用してスレッドを開始しています

CWinThread *pMyThread = AfxBeginThread(CMyThreadFunc,&MyData,THREAD_PRIORITY_NORMAL);

通常、64 ビット アーキテクチャでは 8 コアで 32 スレッドを使用します。問題のプロセスは現在 1 秒未満で完了し、表示が更新されるたびに起動されます。スレッドの開始と終了が 1 ミリ秒未満の場合、リターンは努力を正当化しません。私はこれをプロファイリングするのに苦労しています。

関連する質問 hereが役立ちますが、私が求めているものについては少しあいまいです。フィードバックをいただければ幸いです。

4

3 に答える 3

18

私はかなり前に、同じ基本的な質問があったときにこれを書きました(明らかな別の質問とともに)。スレッドの作成にかかる時間だけでなく、スレッドが実行を開始するのにかかる時間についてももう少し表示するように更新しました。

#include <windows.h>
#include <iostream>
#include <time.h>
#include <vector>

const int num_threads = 32;

const int switches_per_thread = 100000;

DWORD __stdcall ThreadProc(void *start) {
    QueryPerformanceCounter((LARGE_INTEGER *) start);
    for (int i=0;i<switches_per_thread; i++)
        Sleep(0);
    return 0;
}

int main(void) {
    HANDLE threads[num_threads];
    DWORD junk;

    std::vector<LARGE_INTEGER> start_times(num_threads);

    LARGE_INTEGER l;
    QueryPerformanceCounter(&l);

    clock_t create_start = clock();
    for (int i=0;i<num_threads; i++)
        threads[i] = CreateThread(NULL, 
                            0, 
                            ThreadProc, 
                            (void *)&start_times[i], 
                            0, 
                            &junk);
    clock_t create_end = clock();

    clock_t wait_start = clock();
    WaitForMultipleObjects(num_threads, threads, TRUE, INFINITE);
    clock_t wait_end = clock();

    double create_millis = 1000.0 * (create_end - create_start) / CLOCKS_PER_SEC / num_threads;
    std::cout << "Milliseconds to create thread: " << create_millis << "\n";
    double wait_clocks = (wait_end - wait_start);
    double switches = switches_per_thread*num_threads;
    double us_per_switch = wait_clocks/CLOCKS_PER_SEC*1000000/switches;
    std::cout << "Microseconds per thread switch: " << us_per_switch;

    LARGE_INTEGER f;
    QueryPerformanceFrequency(&f);

    for (auto s : start_times) 
        std::cout << 1000.0 * (s.QuadPart - l.QuadPart) / f.QuadPart <<" ms\n";

    return 0;
}

サンプル結果:

Milliseconds to create thread: 0.015625
Microseconds per thread switch: 0.0479687

最初のいくつかのスレッド開始時間は次のようになります。

0.0632517 ms
0.117348 ms
0.143703 ms
0.18282 ms
0.209174 ms
0.232478 ms
0.263826 ms
0.315149 ms
0.324026 ms
0.331516 ms
0.3956 ms
0.408639 ms
0.4214 ms

これらはたまたま単調に増加していますが、それが保証されているわけではないことに注意してください(ただし、その一般的な方向には間違いなく傾向があります)。

私が最初にこれを書いたとき、私が使用した単位はより理にかなっていました.33 MHz 486では、これらの結果はこのような小さな分数ではありませんでした. :-) いつか野心的な気分になったら、これを書き直してstd::async、スレッドの作成とstd::chronoタイミングの実行に使用する必要があると思いますが...

于 2013-08-16T14:17:16.573 に答える
4

いくつかのアドバイス:

  1. 処理する作業項目が多数ある場合 (または、それほど多くはないが、プロセス全体を時々繰り返す必要がある場合) は、何らかのスレッド プーリングを使用するようにしてください。こうすれば、常にスレッドを再作成する必要がなくなり、元の質問はもはや問題になりません。スレッドは 1 回だけ作成されます。私は QueueUserWorkItem API を直接使用しています (私のアプリケーションは MFC を使用していないため)。ただし、MFC では、スレッド プーリングを利用するためのより高いレベルの機能を使用できる場合があります。( http://support.microsoft.com/kb/197728 )
  2. 1 つの作業項目に最適な作業量を選択するようにしてください。もちろん、これはソフトウェアの機能によって異なります。リアルタイムであるべきか、それともバックグラウンドで計算されているか? リアルタイムでない場合、作業項目ごとの作業量が少なすぎると、パフォーマンスが低下する可能性があります。これは、スレッド間の作業分散のオーバーヘッドの割合が増加するためです。
  3. ハードウェア構成は非常に異なる可能性があるため、エンド ユーザーがさまざまなマシンを使用できる場合は、ソフトウェアの開始時に非同期でキャリブレーション ルーチンを含めることができます。これにより、特定の操作にかかる時間を見積もることができます。キャリブレーションの結果は、後で実際の計算を行うための、より適切なワーク サイズ設定の入力となる可能性があります。
于 2013-08-16T19:16:00.240 に答える
1

最新の Windows スケジューラに興味があったので、別のテスト アプリを作成しました。オプションで監視スレッドをスピンアップして、スレッド停止時間を測定するために最善を尽くしました。

// Tested on Windows 10 v1903 with E5-1660 v3 @ 3.00GHz, 8 Core(s), 16 Logical Processor(s)
// Times are (min, average, max) in milliseconds.

threads: 100, iterations: 1, testStop: true
Start(0.1083, 5.3665, 13.7103) - Stop(0.0341, 1.5122, 11.0660)

threads: 32, iterations: 3, testStop: true
Start(0.1349, 1.6423, 3.5561) - Stop(0.0396, 0.2877, 3.5195)
Start(0.1093, 1.4992, 3.3982) - Stop(0.0351, 0.2734, 2.0384)
Start(0.1159, 1.5345, 3.5754) - Stop(0.0378, 0.4938, 3.2216)

threads: 4, iterations: 3, testStop: true
Start(0.2066, 0.3553, 0.4598) - Stop(0.0410, 0.1534, 0.4630)
Start(0.2769, 0.3740, 0.4994) - Stop(0.0414, 0.1028, 0.2581)
Start(0.2342, 0.3602, 0.5650) - Stop(0.0497, 0.2199, 0.3620)

threads: 4, iterations: 3, testStop: false
Start(0.1698, 0.2492, 0.3713)
Start(0.1473, 0.2477, 0.4103)
Start(0.1756, 0.2909, 0.4295)

threads: 1, iterations: 10, testStop: false
Start(0.1910, 0.1910, 0.1910)
Start(0.1685, 0.1685, 0.1685)
Start(0.1564, 0.1564, 0.1564)
Start(0.1504, 0.1504, 0.1504)
Start(0.1389, 0.1389, 0.1389)
Start(0.1234, 0.1234, 0.1234)
Start(0.1550, 0.1550, 0.1550)
Start(0.2800, 0.2800, 0.2800)
Start(0.1587, 0.1587, 0.1587)
Start(0.1877, 0.1877, 0.1877)

ソース:

#include <windows.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <iomanip>

using namespace std::chrono;

struct Test
{
    HANDLE Thread = { 0 };
    time_point<steady_clock> Creation;
    time_point<steady_clock> Started;
    time_point<steady_clock> Stopped;
};

DWORD __stdcall ThreadProc(void* lpParamater) {
    auto test = (Test*)lpParamater;
    test->Started = steady_clock::now();
    return 0;
}

DWORD __stdcall TestThreadsEnded(void* lpParamater) {
    auto& tests = *(std::vector<Test>*)lpParamater;

    std::size_t finished = 0;
    while (finished < tests.size())
    {
        for (auto& test : tests)
        {
            if (test.Thread != NULL && WaitForSingleObject(test.Thread, 0) == WAIT_OBJECT_0)
            {
                test.Stopped = steady_clock::now();
                test.Thread = NULL;
                finished++;
            }
        }
    }

    return 0;
}

duration<double, std::milli> diff(time_point<steady_clock> start, time_point<steady_clock> stop)
{
    return stop - start;
}

struct Stats
{
    double min;
    double average;
    double max;
};

Stats stats(const std::vector<double>& durations)
{
    Stats stats = { 1000, 0, 0 };

    for (auto& duration : durations)
    {
        stats.min = duration < stats.min ? duration : stats.min;
        stats.max = duration > stats.max ? duration : stats.max;
        stats.average += duration;
    }

    stats.average /= durations.size();

    return stats;
}

void TestScheduler(const int threadCount, const int iterations, const bool testStop)
{
    std::cout << "\nthreads: " << threadCount << ", iterations: " << iterations << ", testStop: " << (testStop ? "true" : "false") << "\n";

    for (auto i = 0; i < iterations; i++)
    {
        std::vector<Test> tests(threadCount);
        HANDLE testThreadsEnded = NULL;

        if (testStop)
        {
            testThreadsEnded = CreateThread(NULL, 0, TestThreadsEnded, (void*)& tests, 0, NULL);
        }

        for (auto& test : tests)
        {
            test.Creation = steady_clock::now();
            test.Thread = CreateThread(NULL, 0, ThreadProc, (void*)& test, 0, NULL);
        }

        if (testStop)
        {
            WaitForSingleObject(testThreadsEnded, INFINITE);
        }
        else
        {
            std::vector<HANDLE> threads;
            for (auto& test : tests) threads.push_back(test.Thread);
            WaitForMultipleObjects((DWORD)threads.size(), threads.data(), TRUE, INFINITE);
        }

        std::vector<double> startDurations;
        std::vector<double> stopDurations;
        for (auto& test : tests)
        {
            startDurations.push_back(diff(test.Creation, test.Started).count());
            stopDurations.push_back(diff(test.Started, test.Stopped).count());
        }

        auto startStats = stats(startDurations);
        auto stopStats = stats(stopDurations);

        std::cout << std::fixed << std::setprecision(4);
        std::cout << "Start(" << startStats.min << ", " << startStats.average << ", " << startStats.max << ")";
        if (testStop)
        {
            std::cout << " - ";
            std::cout << "Stop(" << stopStats.min << ", " << stopStats.average << ", " << stopStats.max << ")";
        }
        std::cout << "\n";
    }
}

int main(void)
{
    TestScheduler(100, 1, true);
    TestScheduler(32, 3, true);
    TestScheduler(4, 3, true);
    TestScheduler(4, 3, false);
    TestScheduler(1, 10, false);
    return 0;
}
于 2019-07-11T23:23:54.673 に答える