0

OpenMP を使用してマルチプロセッシングを利用するために、教育目的で少し前に書いたレイトレーサーを変更しました。ただし、並列化による利益は見られません。

私は 3 つの異なるアプローチを試しました。タスク プール環境 (draw_pooled()関数)、イメージの行レベルの並列処理を使用した標準の OMP 並列ネストforループ ( )、およびピクセル レベルの並列処理を使用したdraw_parallel_for()別の OMP 並列処理 ( ) です。オリジナルのシリアル描画ルーチンも参照用に含まれています ( )。fordraw_parallel_for2()draw_serial()

Intel Core 2 Duo E6750 (2 コア @ 2,67 GHz それぞれハイパースレッディング付き) で 2560x1920 のレンダリングを実行しており、Linux では 4 GB の RAM を使用し、libgomp を使用して gcc でバイナリをコンパイルしています。シーンの平均は次のとおりです。

  • 一連のレンダリングに 120 秒、
  • しかし、上記の 3 つの特定の方法のどれを選択しても、2 つのスレッド (デフォルト - CPU コアの数) で並行してこれを行うには196 秒 ( sic! ) かかります。
  • HT を考慮して OMP のデフォルトのスレッド番号を 4 にオーバーライドすると、並列レンダリング時間は 177 秒に短縮されます。

なぜこうなった?並列コードに明らかなボトルネックは見当たりません。

編集:明確にするために-タスクプールは実装の1つにすぎません。質問を読んでください-下にスクロールして、並列を表示しforます。問題は、タスクプールと同じくらい遅いということです!

void draw_parallel_for(int w, int h, const char *fname) {
    unsigned char *buf;

    buf = new unsigned char[w * h * 3];

    Scene::GetInstance().PrepareRender(w, h);

    for (int y = 0; y < h; ++y) {
        #pragma omp parallel for num_threads(4)
        for (int x = 0; x < w; ++x)
            Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
    }

    write_png(buf, w, h, fname);

    delete [] buf;
}

void draw_parallel_for2(int w, int h, const char *fname) {
    unsigned char *buf;

    buf = new unsigned char[w * h * 3];

    Scene::GetInstance().PrepareRender(w, h);

    int x, y;
    #pragma omp parallel for private(x, y) num_threads(4)
    for (int xy = 0; xy < w * h; ++xy) {
        x = xy % w;
        y = xy / w;
        Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
    }

    write_png(buf, w, h, fname);

    delete [] buf;
}

void draw_parallel_for3(int w, int h, const char *fname) {
    unsigned char *buf;

    buf = new unsigned char[w * h * 3];

    Scene::GetInstance().PrepareRender(w, h);

    #pragma omp parallel for num_threads(4)
    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x)
            Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
    }

    write_png(buf, w, h, fname);

    delete [] buf;
}


void draw_serial(int w, int h, const char *fname) {
    unsigned char *buf;

    buf = new unsigned char[w * h * 3];

    Scene::GetInstance().PrepareRender(w, h);

    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x)
            Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
    }

    write_png(buf, w, h, fname);

    delete [] buf;
}

std::queue< std::pair<int, int> * > task_queue;

void draw_pooled(int w, int h, const char *fname) {
    unsigned char *buf;

    buf = new unsigned char[w * h * 3];

    Scene::GetInstance().PrepareRender(w, h);

    bool tasks_issued = false;
    #pragma omp parallel shared(buf, tasks_issued, w, h) num_threads(4)
    {
        #pragma omp master
        {
            for (int y = 0; y < h; ++y) {
                for (int x = 0; x < w; ++x)
                    task_queue.push(new std::pair<int, int>(x, y));
            }
            tasks_issued = true;
        }

        while (true) {
            std::pair<int, int> *coords;
            #pragma omp critical(task_fetch)
            {
                if (task_queue.size() > 0) {
                    coords = task_queue.front();
                    task_queue.pop();
                } else
                    coords = NULL;
            }

            if (coords != NULL) {
                Scene::GetInstance().RenderPixel(coords->first, coords->second,
                    buf + (coords->second * w + coords->first) * 3);
                delete coords;
            } else {
                #pragma omp flush(tasks_issued)
                if (tasks_issued)
                    break;
            }
        }
    }

    write_png(buf, w, h, fname);

    delete [] buf;
}
4

3 に答える 3

3

最も内側のループ内にクリティカル セクションがあります。つまり、ピクセルごとに同期プリミティブをヒットしています。それはパフォーマンスを殺します。

シーンをタイルに分割し、各スレッドで 1 つずつ作業することをお勧めします。そうすれば、同期間の時間が長くなります (タイル全体の処理に相当)。

于 2012-07-02T19:10:22.880 に答える
0

ピクセルが独立している場合、実際にはロックは必要ありません。画像を行または列に分割して、スレッドを単独で機能させることができます。たとえば、各スレッドをn番目の行ごとに動作させることができます(擬似コード)。

for(int y = TREAD_NUM; y < h; y += THREAD_COUNT)
    for(int x = 0; x < w; ++x)
        render_pixel(x,y);

ここで、THREAD_NUMは、のような各スレッドの一意の番号です0 <= THREAD_NUM < THREAD_COUNT。次に、スレッドプールに参加した後、png変換を実行します。

于 2012-07-02T19:45:25.580 に答える
0

スレッドの作成中は常にパフォーマンスのオーバーヘッドが発生します。for ループ内の OMP Parallel は明らかに多くのオーバーヘッドを生成します。たとえば、コードで

void draw_parallel_for(int w, int h, const char *fname) {

    for (int y = 0; y < h; ++y) {

    // Here There is a lot of overhead
         #pragma omp parallel for num_threads(4)
         for (int x = 0; x < w; ++x)
              Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
    }
 }

のように書き直すことができる.

void draw_parallel_for(int w, int h, const char *fname) {


    #pragma omp parallel for num_threads(4)
    for (int y = 0; y < h; ++y) {
           for (int x = 0; x < w; ++x)
              Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
    }
 }

また

void draw_parallel_for(int w, int h, const char *fname) {


    #pragma omp parallel num_threads(4)
    for (int y = 0; y < h; ++y) {
           #pragma omp for
           for (int x = 0; x < w; ++x)
              Scene::GetInstance().RenderPixel(x, y, buf + (y * w + x) * 3);
    }
 }

このようにして、オーバーヘッドを排除します

于 2012-07-02T20:16:25.410 に答える