2017-03-27 3 views
0

私の光線追跡器は現在マルチスレッド化されています。基本的には、画像をシステムと同じ数のチャンクに分割し、それらを平行にレンダリングします。ただし、すべてのチャンクが同じレンダリング時間を持つわけではないため、ほとんどの場合、実行時間の半分は50%のCPU使用率です。私は16×16チャンクまたは類似した何かに画像を分割し、paralellyそれらをレンダリングするので、各チャンクがレンダリングされる後に、その次とのスレッド切り替えたい16x16チャンクでの平行光線追跡

Code

std::shared_ptr<bitmap_image> image = std::make_shared<bitmap_image>(WIDTH, HEIGHT); 
    auto nThreads = std::thread::hardware_concurrency(); 

    std::cout << "Resolution: " << WIDTH << "x" << HEIGHT << std::endl; 
    std::cout << "Supersampling: " << SUPERSAMPLING << std::endl; 
    std::cout << "Ray depth: " << DEPTH << std::endl; 
    std::cout << "Threads: " << nThreads << std::endl; 

    std::vector<RenderThread> renderThreads(nThreads); 
    std::vector<std::thread> tt; 

    auto size = WIDTH*HEIGHT; 

    auto chunk = size/nThreads; 
    auto rem = size % nThreads; 

    //launch threads 
    for (unsigned i = 0; i < nThreads - 1; i++) 
    { 
     tt.emplace_back(std::thread(&RenderThread::LaunchThread, &renderThreads[i], i * chunk, (i + 1) * chunk, image)); 
    } 
    tt.emplace_back(std::thread(&RenderThread::LaunchThread, &renderThreads[nThreads-1], (nThreads - 1)*chunk, nThreads*chunk + rem, image)); 

for (auto& t : tt) 
     t.join(); 

...これcpuの使用量と実行時間が大幅に増加します。

レイ・トレーサは、これらの16x16チャンクをマルチスレッド方式でレンダリングするように設定するにはどうすればよいですか?

+0

何が問題なのですか? –

+0

私の質問は、16x16チャンクに分割してスレッドにキューする方法です。 –

答えて

1

"ブロックをさまざまなスレッドに配布する方法は?"

現在のソリューションでは、領域を事前に把握してスレッドに割り当てています。トリックは、このアイデアを頭でつけることです。彼らは仕事の塊を終了するたびにスレッドが次に何をするかを尋ねる。

ここでスレッドが何をするかの概要です:

void WorkerThread(Manager *manager) { 
    while (auto task = manager->GetTask()) { 
    task->Execute(); 
    } 
} 

ですから、(タスクの形で)スレッドがそのGetTaskメソッドを呼び出すたびに、仕事のチャンクを返すManagerオブジェクトを作成します。そのメソッドは複数のスレッドから呼び出されるので、適切な同期を使用することを確認する必要があります。

std::unique_ptr<Task> Manager::GetTask() { 
    std::lock_guard guard(mutex); 
    std::unique_ptr<Task> t; 
    if (next_row < HEIGHT) { 
     t = std::make_unique<Task>(next_row); 
     ++next_row; 
    } 
    return t; 
} 

この例では、マネージャは次の行を光線追跡する新しいタスクを作成します。すべてのタスクが発行されると、空のポインタが返されます。これは本質的に呼び出し元スレッドに何も残さないことを通知し、呼び出しスレッドは終了します。

すべてのタスクをあらかじめ作成し、マネージャが要求どおりにそれを実行した場合、これは典型的な「ワークキュー」ソリューションになります。 (一般的な作業キューは、新しいタスクがその場で追加することができますが、あなたは、この特定の問題のためにその機能を必要としない)

+0

グリッドを生成し、このタイプのタスク実装にどのように適応させるのですか? –

+0

これは単なる簿記です。各タスクはレンダリングされるべきピクセルのブロックの座標を追跡し、そのExecuteメソッドはそのブロック内のピクセルをループします。私の経験では、画像を行に分割するだけで、すべてのコアを最大限に活用するのに十分です。私はそれから始めます。 –

0

私は少し違った次の操作を行います。

  1. は番号を取得CPUおよびまたはコア

    のあなたは、あなたがこのためにあなたのOS APIを使用する必要がOSが指定されていませんでした。システムアフィニティマスクを検索します。私はQUEか何かを持っている必要はありませんので、私が代わりに16×16ブロックの線で画面を分割していたスレッド

  2. 分割画面。それぞれのCPU /コアのスレッドを作成するだけで、水平線だけを処理します。

    i={0,1,2,3,... }y後は、画面の解像度停止より大きいか等しい
    y = ID + i*n 
    

    :すなわち、各プロセスに属する線であるようので、各スレッドは、そのIDゼロからカウント数とCPU /コアの数nを有していなければならない簡単です。このタイプのアクセスには利点があります。たとえば、ScanLines経由でスクリーンバッファにアクセスすると、各スレッドがそのラインのみにアクセスするため、スレッド間で競合することはありません... CPUを使用するように各スレッドにアフィニティマスクも設定します。/coreこれは私に小さなブーストを与えますので、それほど多くのプロセスを切り替えることはできません(しかし、これは古いものでした。OSバージョンは今のところ難しいです)。すべてのスレッドが終了するまで

  3. 同期スレッド

    は基本的に、あなたは待つ必要があります。結果がスクリーン上にレンダリングされている場合。スレッドは停止するか、次のフレームで新しいスレッドを作成するか、Sleepループにジャンプして再度レンダリングされます...

    私は後者のアプローチを使用しているため、スレッドを繰り返し作成して設定する必要はありません再びSleep(1)はもっと多くの睡眠をすることができます。ただ1 msです。