2

少しの背景 - 私は次のセットアップを実行しています:

  • i5 8300H (4 コア、8 スレッド)
  • 32GBのRAM
  • Ubuntu 19.10
  • GCC 9.2.1、C++17 標準

私はスレッド マネージャーを持っています - 基本的にデータを中継できるオブジェクトです。呼び出し可能なオブジェクトを指定すると、タスクを並行して実行できます。スレッド マネージャーにはスレッドをタイムアウトにする機能があります (一部のタスクがハングした場合) 、私がやっていることの場合に当てはまる可能性があるため)、バッチでデータを提供するなど.

この動作の疑似コードは次のとおりです。

function do_tasks(task, data, batch_size, timeout, threads, output_streams):
    assert arguments_are_valid()

    failed_tasks = []

    while(true):
        if data.size() == 0:
            break

        for thread in threads:
            if thread.running():
                stop_thread(thread)

            if thread.results.size() == 0:
                failed_tasks <- failed_tasks + thread.given_data
            else:
                data <- data + thread.given_data(data.begin() + thread.results.size(), thread.given_data.end())

            start_thread(thread, task, take_data(data, min(batch_size, data.size()))

        wait_for_threads_completed_or_timeout(threads, timeout)

    return failed_tasks

私はエキゾチックなものを使用していません。これはすべて、プレーンな std::thread、std::list、std::future、および std::promise を使用して達成されます。

簡単に言うと、スレッドにデータを渡します。スレッドが行ったことを評価するときに、バッチ全体が失敗した場合 (つまり、データ要素が解決されない場合)、バッチ全体が failed_tasks コンテナーに転送され、後で返されます。これらの失敗したバッチは、後で batch_size を 1 にしてタスクを実行することで解決されます (したがって、タスクがタイムアウトした場合、実際には手動でチェックアウトする必要があります) が、その部分は重要ではありません。少なくとも 1 つのデータ要素が解決された場合、未解決の部分をデータ コンテナーに戻します。これは、すべてのデータ要素が解決されるか、failed_tasks としてマークされるまで実行されます。

さて、通常、7 つのスレッドで 100000 要素に対してこれを実行するとします。初めて実行すると、最大 2000 個の要素がタイムアウトします。2 回目も同様で、500 ~ 2000 個の要素がタイムアウトします。しかし、ここに奇妙な部分があります-数回実行した後、意図した動作が得られ、約2〜5個のタスクが失敗します。

実行されている関数を見ると、平均シングル スレッドで 1 秒あたり 10500 個のデータ要素を処理できます。その最小実行時間は 1 ナノ秒未満ですが、観察された最大実行時間は数ミリ秒です (データを正規表現と照合し、多かれ少なかれ DoS 攻撃として機能するシーケンスがあるため、実行が大幅に遅くなる可能性があります)。 . 通常、7 スレッドで実行すると、平均して 1 秒あたり 70000 個のデータ要素を処理できるため、効率は約 95% です。ただし、最初の数回の実行が発生すると、これは 1 秒あたり 55000 データ要素まで低下します。これは約 75% の効率であり、パフォーマンスが大幅に低下します。現在、パフォーマンスはそれほど重要ではありません (1 秒あたり 20000 個のデータ要素を処理する必要があり、タスク 2 スレッドで十分です)。

私はこれを読みました:

マルチスレッド処理でスレッドを「ウォームアップ」することは実際には何ですか?

しかし、動作はJITインタープリターによって引き起こされているようです.C++にはコンパイルされていないものがあります。std::thread のオーバーヘッドについては知っていますが、それほど大きくないと思われます。ここで経験しているのはウォームアップに似ていますが、スレッドにウォームアップ期間があるとは聞いたことがありません。この動作は、データを変更しても(実行ごとに異なるデータセット)一貫しているため、高速化するキャッシュが行われていないと思われます。

実装はおそらく正しく、レビューと正式なテストが行​​われています。コードの大部分は C および C++ であり、積極的にメンテナンスされているため、これはバグではないと思われます。しかし、インターネット上で同じ問題を抱えている人を見つけることができなかったので、私たちが見逃しているものがあるのではないかと思いました.

なぜこのウォームアップが起こるのか、誰にも分かりますか?

編集:作業は次のように実行されます。

for(ull i = 0; i != batch_size && future.wait_for(nanoseconds(0)) == future_status::timeout; ++i)
{
    //do stuff
}

スレッドによって実行される関数は、スレッドが次のデータ要素でタスクを実行する前にチェックできるフューチャーを受け取ります。ここではフューチャーと呼ばれます。

4

0 に答える 0